browser-web-search 0.2.3 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -6,7 +6,7 @@
6
6
 
7
7
  - **零配置** — 无需 Chrome Extension、无需 Daemon,开箱即用
8
8
  - **复用登录态** — 使用浏览器中的登录状态,无需 API Key
9
- - **13 个平台,41 个命令** — 覆盖搜索、社交、新闻、开发、视频、招聘等场景
9
+ - **17 个平台,37 个命令** — 覆盖搜索、社交、新闻、视频、招聘等场景
10
10
  - **AI Agent 友好** — 结构化 JSON 输出,内置 jq 过滤
11
11
 
12
12
  ## 安装
@@ -25,20 +25,24 @@ bws site zhihu/hot # 知乎热榜
25
25
  bws site bilibili/popular # B站热门视频
26
26
  ```
27
27
 
28
- ## 内置平台(13 个)
28
+ ## 内置平台(17 个)
29
29
 
30
30
  | 平台 | 说明 | 命令 |
31
31
  |-----|------|-----|
32
- | **今日头条** | 新闻资讯 | `toutiao/hot`, `toutiao/search` |
33
- | **小红书** | 生活分享 | `xiaohongshu/search`, `xiaohongshu/note`, `xiaohongshu/comments`, `xiaohongshu/user_posts` |
34
- | **36kr** | 科技创投 | `36kr/newsflash` |
32
+ | **今日头条** | 新闻资讯 | `toutiao/hot`, `toutiao/search`, `toutiao/feed` |
33
+ | **澎湃新闻** | 权威新闻 | `thepaper/hot` |
34
+ | **腾讯新闻** | 热点新闻 | `qqnews/hot` |
35
+ | **网易新闻** | 热点新闻 | `netease/hot` |
36
+ | **新浪新闻** | 门户新闻 | `sina/hot` |
37
+ | **微博** | 社交热搜 | `weibo/hot` |
38
+ | **微信公众号** | 公众号文章 | `weixin/search`, `weixin/article` |
39
+ | **小红书** | 生活分享 | `xiaohongshu/search`, `xiaohongshu/note`, `xiaohongshu/comments`, `xiaohongshu/feed`, `xiaohongshu/me`, `xiaohongshu/user_posts` |
40
+ | **36kr** | 科技创投 | `36kr/newsflash`, `36kr/search`, `36kr/article` |
35
41
  | **知乎** | 问答社区 | `zhihu/hot`, `zhihu/search`, `zhihu/question`, `zhihu/me` |
36
42
  | **CSDN** | 开发者社区 | `csdn/search` |
37
43
  | **博客园** | 技术博客 | `cnblogs/search` |
38
- | **豆瓣** | 影视书籍 | `douban/movie`, `douban/search`, `douban/top250`, `douban/movie-hot`, `douban/movie-top`, `douban/comments` |
39
44
  | **Bilibili** | 视频弹幕 | `bilibili/popular`, `bilibili/trending`, `bilibili/ranking`, `bilibili/search`, `bilibili/video`, `bilibili/comments`, `bilibili/feed`, `bilibili/history`, `bilibili/me` |
40
45
  | **BOSS直聘** | 招聘平台 | `boss/search`, `boss/detail` |
41
- | **GitHub** | 代码托管 | `github/repo`, `github/issues`, `github/fork`, `github/pr-create`, `github/issue-create` |
42
46
  | **Baidu** | 百度搜索 | `baidu/search` |
43
47
  | **Bing** | 必应搜索 | `bing/search` |
44
48
  | **Google** | 谷歌搜索 | `google/search` |
@@ -65,8 +69,8 @@ bws site xiaohongshu/search "美食" --jq '.notes[].title'
65
69
  # B站热门视频 JSON
66
70
  bws site bilibili/popular --json
67
71
 
68
- # GitHub 仓库信息
69
- bws site github/repo facebook/react
72
+ # 微博热搜
73
+ bws site weibo/hot
70
74
  ```
71
75
 
72
76
  ## 登录态
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "browser-web-search",
3
- "version": "0.2.3",
3
+ "version": "0.3.1",
4
4
  "description": "把任何网站变成命令行 API,专为 OpenClaw 设计,复用浏览器登录态",
5
5
  "type": "module",
6
6
  "bin": {
@@ -0,0 +1,51 @@
1
+ /* @meta
2
+ {
3
+ "name": "36kr/article",
4
+ "description": "36氪文章详情",
5
+ "domain": "36kr.com",
6
+ "args": {
7
+ "id": {"required": true, "description": "文章ID"}
8
+ },
9
+ "readOnly": true,
10
+ "example": "bws 36kr/article 123456789"
11
+ }
12
+ */
13
+
14
+ async function(args) {
15
+ if (!args.id) return {error: 'Missing argument: id', hint: 'Provide an article ID'};
16
+
17
+ const resp = await fetch('https://36kr.com/api/post/' + args.id, {
18
+ credentials: 'include'
19
+ });
20
+
21
+ if (!resp.ok) {
22
+ return {error: 'HTTP ' + resp.status, hint: 'Article may not exist or 36kr.com needs login'};
23
+ }
24
+
25
+ let data;
26
+ try {
27
+ data = await resp.json();
28
+ } catch (e) {
29
+ return {error: 'Failed to parse response', hint: '36kr API may have changed'};
30
+ }
31
+
32
+ if (!data || data.code !== 0 || !data.data) {
33
+ return {error: data.message || 'Article not found', hint: 'Check the article ID'};
34
+ }
35
+
36
+ const article = data.data;
37
+ return {
38
+ id: article.id,
39
+ title: article.title || '',
40
+ summary: article.summary || '',
41
+ content: article.content || article.text || '',
42
+ author: article.author?.name || article.user?.name || '',
43
+ published_at: article.published_at || article.created_at || '',
44
+ url: 'https://36kr.com/p/' + article.id,
45
+ cover: article.cover || '',
46
+ views: article.views_count || 0,
47
+ comments: article.comments_count || 0,
48
+ likes: article.likes_count || 0,
49
+ tags: (article.tags || []).map(t => t.name || t)
50
+ };
51
+ }
@@ -0,0 +1,55 @@
1
+ /* @meta
2
+ {
3
+ "name": "36kr/search",
4
+ "description": "36氪文章搜索",
5
+ "domain": "36kr.com",
6
+ "args": {
7
+ "keyword": {"required": true, "description": "搜索关键词"},
8
+ "count": {"required": false, "description": "返回数量 (默认 20, 最多 50)"}
9
+ },
10
+ "readOnly": true,
11
+ "example": "bws 36kr/search AI"
12
+ }
13
+ */
14
+
15
+ async function(args) {
16
+ if (!args.keyword) return {error: 'Missing argument: keyword', hint: 'Provide a search keyword'};
17
+ const count = Math.min(parseInt(args.count) || 20, 50);
18
+
19
+ const resp = await fetch('https://36kr.com/api/search-column/mainsite?per_page=' + count + '&page=1&keyword=' + encodeURIComponent(args.keyword), {
20
+ credentials: 'include'
21
+ });
22
+
23
+ if (!resp.ok) {
24
+ return {error: 'HTTP ' + resp.status, hint: 'Open 36kr.com in browser first'};
25
+ }
26
+
27
+ let data;
28
+ try {
29
+ data = await resp.json();
30
+ } catch (e) {
31
+ return {error: 'Failed to parse response', hint: '36kr API may have changed'};
32
+ }
33
+
34
+ if (!data || data.code !== 0 || !data.data) {
35
+ return {error: data.message || 'No data returned', hint: 'Try a different keyword'};
36
+ }
37
+
38
+ const items = (data.data.items || []).map(item => ({
39
+ id: item.id,
40
+ title: item.title || '',
41
+ summary: item.summary || item.description || '',
42
+ author: item.author || item.user?.name || '',
43
+ published_at: item.published_at || item.created_at || '',
44
+ url: 'https://36kr.com/p/' + item.id,
45
+ cover: item.cover || '',
46
+ views: item.views_count || 0,
47
+ comments: item.comments_count || 0
48
+ }));
49
+
50
+ return {
51
+ keyword: args.keyword,
52
+ count: items.length,
53
+ items
54
+ };
55
+ }
@@ -0,0 +1,51 @@
1
+ /* @meta
2
+ {
3
+ "name": "netease/hot",
4
+ "description": "网易新闻热榜",
5
+ "domain": "www.163.com",
6
+ "args": {
7
+ "count": {"required": false, "description": "返回数量 (默认 20, 最多 50)"}
8
+ },
9
+ "readOnly": true,
10
+ "example": "bws netease/hot"
11
+ }
12
+ */
13
+
14
+ async function(args) {
15
+ const maxCount = Math.min(parseInt(args.count) || 20, 50);
16
+
17
+ const resp = await fetch('https://m.163.com/fe/api/hot/news/flow', {
18
+ credentials: 'include'
19
+ });
20
+
21
+ if (!resp.ok) {
22
+ return {error: 'HTTP ' + resp.status, hint: 'Open www.163.com in browser first'};
23
+ }
24
+
25
+ let data;
26
+ try {
27
+ data = await resp.json();
28
+ } catch (e) {
29
+ return {error: 'Failed to parse response', hint: 'Netease API may have changed'};
30
+ }
31
+
32
+ if (!data || !data.data || !data.data.list) {
33
+ return {error: 'No data returned', hint: 'Netease API may have changed'};
34
+ }
35
+
36
+ const items = data.data.list.slice(0, maxCount).map((item, i) => ({
37
+ rank: i + 1,
38
+ id: item.docid,
39
+ title: item.title || '',
40
+ cover: item.imgsrc || '',
41
+ source: item.source || '',
42
+ time: item.ptime || '',
43
+ url: 'https://www.163.com/dy/article/' + item.docid + '.html',
44
+ mobileUrl: 'https://m.163.com/dy/article/' + item.docid + '.html'
45
+ }));
46
+
47
+ return {
48
+ count: items.length,
49
+ items
50
+ };
51
+ }
@@ -0,0 +1,53 @@
1
+ /* @meta
2
+ {
3
+ "name": "qqnews/hot",
4
+ "description": "腾讯新闻热榜",
5
+ "domain": "news.qq.com",
6
+ "args": {
7
+ "count": {"required": false, "description": "返回数量 (默认 20, 最多 50)"}
8
+ },
9
+ "readOnly": true,
10
+ "example": "bws qqnews/hot"
11
+ }
12
+ */
13
+
14
+ async function(args) {
15
+ const maxCount = Math.min(parseInt(args.count) || 20, 50);
16
+
17
+ const resp = await fetch('https://r.inews.qq.com/gw/event/hot_ranking_list?page_size=50', {
18
+ credentials: 'include'
19
+ });
20
+
21
+ if (!resp.ok) {
22
+ return {error: 'HTTP ' + resp.status, hint: 'Open news.qq.com in browser first'};
23
+ }
24
+
25
+ let data;
26
+ try {
27
+ data = await resp.json();
28
+ } catch (e) {
29
+ return {error: 'Failed to parse response', hint: 'QQ News API may have changed'};
30
+ }
31
+
32
+ if (!data || !data.idlist || !data.idlist[0] || !data.idlist[0].newslist) {
33
+ return {error: 'No data returned', hint: 'QQ News API may have changed'};
34
+ }
35
+
36
+ const list = data.idlist[0].newslist.slice(1, maxCount + 1);
37
+ const items = list.map((item, i) => ({
38
+ rank: i + 1,
39
+ id: item.id,
40
+ title: item.title || '',
41
+ desc: item.abstract || '',
42
+ cover: item.miniProShareImage || '',
43
+ source: item.source || '',
44
+ hot: item.hotEvent?.hotScore || 0,
45
+ url: 'https://new.qq.com/rain/a/' + item.id,
46
+ mobileUrl: 'https://view.inews.qq.com/k/' + item.id
47
+ }));
48
+
49
+ return {
50
+ count: items.length,
51
+ items
52
+ };
53
+ }
@@ -0,0 +1,86 @@
1
+ /* @meta
2
+ {
3
+ "name": "sina/hot",
4
+ "description": "新浪新闻热榜",
5
+ "domain": "news.sina.com.cn",
6
+ "args": {
7
+ "type": {"required": false, "description": "分类: all/china/world/society/sports/finance/ent/tech/mil (默认 all)"},
8
+ "count": {"required": false, "description": "返回数量 (默认 20, 最多 50)"}
9
+ },
10
+ "readOnly": true,
11
+ "example": "bws sina/hot tech"
12
+ }
13
+ */
14
+
15
+ async function(args) {
16
+ const typeMap = {
17
+ 'all': {www: 'news', params: 'www_www_all_suda_suda'},
18
+ 'china': {www: 'news', params: 'news_china_suda'},
19
+ 'world': {www: 'news', params: 'news_world_suda'},
20
+ 'society': {www: 'news', params: 'news_society_suda'},
21
+ 'sports': {www: 'sports', params: 'sports_suda'},
22
+ 'finance': {www: 'finance', params: 'finance_0_suda'},
23
+ 'ent': {www: 'ent', params: 'ent_suda'},
24
+ 'tech': {www: 'tech', params: 'tech_news_suda'},
25
+ 'mil': {www: 'news', params: 'news_mil_suda'}
26
+ };
27
+
28
+ const type = args.type || 'all';
29
+ const config = typeMap[type] || typeMap['all'];
30
+ const maxCount = Math.min(parseInt(args.count) || 20, 50);
31
+
32
+ const now = new Date();
33
+ const year = now.getFullYear();
34
+ const month = String(now.getMonth() + 1).padStart(2, '0');
35
+ const day = String(now.getDate()).padStart(2, '0');
36
+ const dateStr = '' + year + month + day;
37
+
38
+ const url = 'https://top.' + config.www + '.sina.com.cn/ws/GetTopDataList.php?top_type=day&top_cat=' + config.params + '&top_time=' + dateStr + '&top_show_num=50';
39
+
40
+ const resp = await fetch(url, {credentials: 'include'});
41
+
42
+ if (!resp.ok) {
43
+ return {error: 'HTTP ' + resp.status, hint: 'Open news.sina.com.cn in browser first'};
44
+ }
45
+
46
+ let text;
47
+ try {
48
+ text = await resp.text();
49
+ } catch (e) {
50
+ return {error: 'Failed to fetch response', hint: 'Sina API may have changed'};
51
+ }
52
+
53
+ // Parse JSONP: var data = {...};
54
+ let data;
55
+ try {
56
+ const prefix = 'var data = ';
57
+ if (!text.startsWith(prefix)) {
58
+ return {error: 'Invalid response format', hint: 'Sina API may have changed'};
59
+ }
60
+ let jsonStr = text.slice(prefix.length).trim();
61
+ if (jsonStr.endsWith(';')) jsonStr = jsonStr.slice(0, -1);
62
+ data = JSON.parse(jsonStr);
63
+ } catch (e) {
64
+ return {error: 'Failed to parse response', hint: 'Sina API may have changed'};
65
+ }
66
+
67
+ if (!data || !data.data) {
68
+ return {error: 'No data returned', hint: 'Sina API may have changed'};
69
+ }
70
+
71
+ const items = data.data.slice(0, maxCount).map((item, i) => ({
72
+ rank: i + 1,
73
+ id: item.id,
74
+ title: item.title || '',
75
+ source: item.media || '',
76
+ hot: parseFloat((item.top_num || '0').replace(/,/g, '')),
77
+ time: (item.create_date || '') + ' ' + (item.create_time || ''),
78
+ url: item.url || ''
79
+ }));
80
+
81
+ return {
82
+ type: type,
83
+ count: items.length,
84
+ items
85
+ };
86
+ }
@@ -0,0 +1,50 @@
1
+ /* @meta
2
+ {
3
+ "name": "thepaper/hot",
4
+ "description": "澎湃新闻热榜",
5
+ "domain": "www.thepaper.cn",
6
+ "args": {
7
+ "count": {"required": false, "description": "返回数量 (默认 20, 最多 50)"}
8
+ },
9
+ "readOnly": true,
10
+ "example": "bws thepaper/hot"
11
+ }
12
+ */
13
+
14
+ async function(args) {
15
+ const maxCount = Math.min(parseInt(args.count) || 20, 50);
16
+
17
+ const resp = await fetch('https://cache.thepaper.cn/contentapi/wwwIndex/rightSidebar', {
18
+ credentials: 'include'
19
+ });
20
+
21
+ if (!resp.ok) {
22
+ return {error: 'HTTP ' + resp.status, hint: 'Open www.thepaper.cn in browser first'};
23
+ }
24
+
25
+ let data;
26
+ try {
27
+ data = await resp.json();
28
+ } catch (e) {
29
+ return {error: 'Failed to parse response', hint: 'Thepaper API may have changed'};
30
+ }
31
+
32
+ if (!data || !data.data || !data.data.hotNews) {
33
+ return {error: 'No data returned', hint: 'Thepaper API may have changed'};
34
+ }
35
+
36
+ const items = data.data.hotNews.slice(0, maxCount).map((item, i) => ({
37
+ rank: i + 1,
38
+ id: item.contId,
39
+ title: item.name || '',
40
+ cover: item.pic || '',
41
+ hot: parseInt(item.praiseTimes) || 0,
42
+ url: 'https://www.thepaper.cn/newsDetail_forward_' + item.contId,
43
+ mobileUrl: 'https://m.thepaper.cn/newsDetail_forward_' + item.contId
44
+ }));
45
+
46
+ return {
47
+ count: items.length,
48
+ items
49
+ };
50
+ }
@@ -0,0 +1,92 @@
1
+ /* @meta
2
+ {
3
+ "name": "toutiao/feed",
4
+ "description": "今日头条分类新闻(支持关键词过滤)",
5
+ "domain": "www.toutiao.com",
6
+ "args": {
7
+ "category": {"required": false, "description": "分类: hot/tech/entertainment/sports/finance/military/world/game/car (默认 hot)"},
8
+ "keyword": {"required": false, "description": "关键词过滤(可选)"},
9
+ "count": {"required": false, "description": "返回数量 (默认 20, 最多 50)"}
10
+ },
11
+ "readOnly": true,
12
+ "example": "bws toutiao/feed tech --keyword AI"
13
+ }
14
+ */
15
+
16
+ async function(args) {
17
+ const categoryMap = {
18
+ 'all': '__all__',
19
+ 'hot': 'news_hot',
20
+ 'tech': 'news_tech',
21
+ 'entertainment': 'news_entertainment',
22
+ 'sports': 'news_sports',
23
+ 'finance': 'news_finance',
24
+ 'military': 'news_military',
25
+ 'world': 'news_world',
26
+ 'game': 'news_game',
27
+ 'car': 'news_car',
28
+ 'society': 'news_society',
29
+ 'fashion': 'news_fashion',
30
+ 'travel': 'news_travel',
31
+ 'history': 'news_history',
32
+ 'food': 'news_food'
33
+ };
34
+
35
+ const category = categoryMap[args.category] || categoryMap['hot'];
36
+ const maxCount = Math.min(parseInt(args.count) || 20, 50);
37
+ const keyword = args.keyword ? args.keyword.toLowerCase() : null;
38
+
39
+ const resp = await fetch('https://www.toutiao.com/api/pc/feed/?category=' + category + '&max_behot_time=0', {
40
+ credentials: 'include'
41
+ });
42
+
43
+ if (!resp.ok) {
44
+ return {error: 'HTTP ' + resp.status, hint: 'Open www.toutiao.com in browser first'};
45
+ }
46
+
47
+ let data;
48
+ try {
49
+ data = await resp.json();
50
+ } catch (e) {
51
+ return {error: 'Failed to parse response', hint: 'Toutiao API may have changed'};
52
+ }
53
+
54
+ if (!data || !data.data) {
55
+ return {error: 'No data returned', hint: 'Open www.toutiao.com in browser first'};
56
+ }
57
+
58
+ const results = [];
59
+ for (const item of data.data) {
60
+ const title = item.title || '';
61
+ const abstract = item.abstract || '';
62
+ const keywords = item.keywords || '';
63
+ const source = item.source || item.media_name || '';
64
+
65
+ // Filter by keyword if provided
66
+ if (keyword) {
67
+ const searchText = (title + ' ' + abstract + ' ' + keywords).toLowerCase();
68
+ if (!searchText.includes(keyword)) continue;
69
+ }
70
+
71
+ results.push({
72
+ title,
73
+ snippet: abstract.substring(0, 300),
74
+ source,
75
+ time: item.datetime || '',
76
+ url: item.article_url || item.display_url || item.share_url || '',
77
+ tag: item.tag || '',
78
+ hot_value: item.hot || 0,
79
+ comment_count: item.comment_count || 0
80
+ });
81
+
82
+ if (results.length >= maxCount) break;
83
+ }
84
+
85
+ return {
86
+ category: args.category || 'hot',
87
+ keyword: args.keyword || null,
88
+ count: results.length,
89
+ total_fetched: data.data.length,
90
+ results
91
+ };
92
+ }
@@ -0,0 +1,56 @@
1
+ /* @meta
2
+ {
3
+ "name": "weibo/hot",
4
+ "description": "微博热搜榜",
5
+ "domain": "weibo.com",
6
+ "args": {
7
+ "count": {"required": false, "description": "返回数量 (默认 30, 最多 50)"}
8
+ },
9
+ "readOnly": true,
10
+ "example": "bws weibo/hot"
11
+ }
12
+ */
13
+
14
+ async function(args) {
15
+ const maxCount = Math.min(parseInt(args.count) || 30, 50);
16
+
17
+ const resp = await fetch('https://weibo.com/ajax/side/hotSearch', {
18
+ credentials: 'include',
19
+ headers: {
20
+ 'Referer': 'https://weibo.com/'
21
+ }
22
+ });
23
+
24
+ if (!resp.ok) {
25
+ return {error: 'HTTP ' + resp.status, hint: 'Open weibo.com in browser first'};
26
+ }
27
+
28
+ let data;
29
+ try {
30
+ data = await resp.json();
31
+ } catch (e) {
32
+ return {error: 'Failed to parse response', hint: 'Weibo API may have changed'};
33
+ }
34
+
35
+ if (!data || !data.data || !data.data.realtime) {
36
+ return {error: 'No data returned', hint: 'Open weibo.com and login first'};
37
+ }
38
+
39
+ const items = data.data.realtime.slice(0, maxCount).map((item, i) => {
40
+ const title = item.word || item.word_scheme || '';
41
+ return {
42
+ rank: i + 1,
43
+ id: item.mid || item.word_scheme || '',
44
+ title: title,
45
+ tag: item.label_name || '',
46
+ hot: item.num || 0,
47
+ url: 'https://s.weibo.com/weibo?q=' + encodeURIComponent(title),
48
+ mobileUrl: 'https://m.weibo.cn/search?containerid=100103type%3D1%26q%3D' + encodeURIComponent(title)
49
+ };
50
+ });
51
+
52
+ return {
53
+ count: items.length,
54
+ items
55
+ };
56
+ }
@@ -0,0 +1,73 @@
1
+ /* @meta
2
+ {
3
+ "name": "weixin/article",
4
+ "description": "获取微信公众号文章内容",
5
+ "domain": "mp.weixin.qq.com",
6
+ "args": {},
7
+ "readOnly": true,
8
+ "example": "bws weixin/article"
9
+ }
10
+ */
11
+
12
+ async function(args) {
13
+ // 检查是否在微信公众号文章页面
14
+ if (!location.href.includes('mp.weixin.qq.com')) {
15
+ return {
16
+ error: 'Not on WeChat article page',
17
+ hint: 'First open the article: openclaw browser open <article_url>'
18
+ };
19
+ }
20
+
21
+ // 检查是否需要验证
22
+ const captchaEl = document.querySelector('.weui-msg__title');
23
+ if (captchaEl && captchaEl.textContent.includes('验证')) {
24
+ return {
25
+ error: 'Captcha required',
26
+ hint: 'Please complete the verification in the browser first'
27
+ };
28
+ }
29
+
30
+ // 提取文章信息
31
+ const titleEl = document.querySelector('#activity-name, .rich_media_title');
32
+ const authorEl = document.querySelector('#js_name, .rich_media_meta_nickname');
33
+ const contentEl = document.querySelector('#js_content, .rich_media_content');
34
+ const publishTimeEl = document.querySelector('#publish_time, .rich_media_meta_text');
35
+
36
+ if (!titleEl || !contentEl) {
37
+ return {
38
+ error: 'Failed to extract article content',
39
+ hint: 'Page structure may have changed or article not fully loaded'
40
+ };
41
+ }
42
+
43
+ // 提取正文文本
44
+ const paragraphs = [];
45
+ const sections = contentEl.querySelectorAll('p, section');
46
+ sections.forEach(el => {
47
+ const text = el.textContent?.trim();
48
+ if (text && text.length > 0) {
49
+ paragraphs.push(text);
50
+ }
51
+ });
52
+
53
+ // 提取图片
54
+ const images = [];
55
+ const imgEls = contentEl.querySelectorAll('img[data-src], img[src]');
56
+ imgEls.forEach(img => {
57
+ const src = img.getAttribute('data-src') || img.src;
58
+ if (src && !src.includes('data:image')) {
59
+ images.push(src);
60
+ }
61
+ });
62
+
63
+ return {
64
+ title: titleEl.textContent?.trim() || '',
65
+ author: authorEl?.textContent?.trim() || '',
66
+ publishTime: publishTimeEl?.textContent?.trim() || '',
67
+ url: location.href,
68
+ content: paragraphs.join('\n\n'),
69
+ paragraphCount: paragraphs.length,
70
+ imageCount: images.length,
71
+ images: images.slice(0, 10)
72
+ };
73
+ }