Featured image of post 微信公众号文章自动收藏:Readwise + 自动化抓取完整方案

微信公众号文章自动收藏:Readwise + 自动化抓取完整方案

你是否像我一样,在日常碎片化时间中翻看公众号,即便遇到不错的文章,但是往往没时间深入细看,或者看过却回头就忘记了?仿佛我们看了个热闹而已,有多少内容转换为有效的输入了呢?本文分享如何用 Readwise Reader 打造个人知识管理系统,实现公众号文章的自动抓取、智能分类和深度阅读。

背景

日常生活中,关于知识的更新,除了一些主动订阅的 Feed 流外,公众号的一批还不错的文章时常能让我扩大一些视野。有人说在短视频热之后,以往做公众号自媒体的一批人切过去了,仍然坚持留下来继续写公众号的人,反而让这块天地有了更纯粹和更高质量了,事实是否如此我不知道。我自己也写公众号,不求有多少人关注,也不想跟随热点,但愿能以文会友,交结到一些志同道合的朋友。

在我日常吃饭、如厕等时候,会翻看一些公众号,有一些较长的文章或者技术密度高的文章,为了不错失某些内容,我常将它悬浮可以随时调出来细读,但咱不明白微信为何限制只能稍后读5篇文章,并且几篇文章的切换和操作体验极差。

有些适合深度阅读的好文,它和我们日常使用手机的方式是冲突的。我个人更喜欢在电脑上,或者在专用软件上查看。我们可以划线留言,可以高亮备注。或许这样,先前的"看过"才不至于流于形式。

Readwise & Reader简介

有个同事给我推荐了 Readwise,我发现它非常好地命中了我的诉求。它的功能非常简单,但却超级实用,如同它的宣传语:

Get the most out of what you read Readwise makes it easy to revisit and learn from your ebook & article highlights.

这个方案解决了我们信息获取和处理的几个关键点,我称之为串联或连接。

Reader主界面

比如,它有浏览器插件,安装后我们可以方便地对正在阅读的网页内容划线、高亮或备注,这一切就会进入到你的 Readwise 账号中。还有一个副产品Reader,我觉得更赞的是,它支持将某个 URL 自动抓取,支持播客,支持传统 RSS,还支持youtube等视频直接在Reader 中观看,字幕同步。当然还可以导入 PDF、EBOOK等,在这里进行更深入的阅读和处理。 在 Reader 中观看youtube

这样,信息的获取和快速处理便解决了。接下来工具不止于此,Readwise 除了提供我们划线或备注的 Review 功能后,更是可以将我们的这些内容与其它外部工具结合,比如相关内容自动同步到 Notion 中。 Readwise笔记等自动导出到Notion 不止是 Notion,几乎市面上常见的主流的笔记软件它都可以导出,像印象笔记、Obsidian甚至 Apple Notes。还有热门的 MCP 等都支持,我只能惊叹。

但显然这是一篇技术文章,不是做广告。毕竟 Readwise出身于国外,互联网平台的技术封锁没有那么强,这个东西好归好,在国内却会有点水土不服了。比如微信公众号文章,你会发现给它链接后,它可能抓取不完整或者缺图片等,怎么办呢?继续往下看呗!

方案一:成熟的第三方工具wechat2reader

咱不是爱造轮子的人,遇上问题当然搜索过。找到这个工具,试用了一下效果还不错。 使用第三方Wechat2Reader工具

但我发现好像也有一些小问题,比如摘要不合理,有一些较长的文章,它不是对全文的摘要,只是文章部分内容的复述。其次标签不准确或者没有,对于快速 Get 文章主要内容的人,有时会观察一下相关标签,如上文,似乎没有生成任何标签,我推测是原文没有标签,所以在抓取保存入 Reader 后也没有标签。最后,它还是比较人性化地提供了 15 天免费试用的机会,但再想要使用,需要支付一定费用(好像年费 45 元,我已经付过表示支持作者)。当然如果你格外介意隐私,可能也会有一些小担心。

那么,作为一个程序员,是时候自己动手来解决这个问题了。

方案二:自动化抓取和导入到 Reader

我希望对于这些文章的抓取是离线的,是Headless的。我的整体思想是先用成熟的组件自己组装一个相关功能,再考虑局部优化。整体交互希望和方案一类似的比较简单直接。

1. 文章抓取和图片处理

之前用过一阵firecrawl MCP来抓取网页,能力还不错,所以我先用它来抓取。在实践之前,咱们先基于MCP来测试一下效果。

Firecrawl 抓取结果示例

1
2
3
目前这个项目在 GitHub 上已经收获了 **39.8k star**。且仍在快速增长。

![](data:image/svg+xml,%3C%3Fxml version='1.0' encoding='UTF-8'%3F%3E%3Csvg width='1px' height='1px' viewBox='0 0 1 1' version='1.1' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink'%3E%3Ctitle%3E%3C/title%3E%3Cg stroke='none' stroke-width='1' fill='none' fill-rule='evenodd' fill-opacity='0'%3E%3Cg transform='translate(-249.000000, -126.000000)' fill='%23FFFFFF'%3E%3Crect x='249' y='126' width='1' height='1'%3E%3C/rect%3E%3C/g%3E%3C/g%3E%3C/svg%3E)

结果不如人意,部分图片可以抓取到,但有部分图片显示为占位符。原因是微信公众号的部分图片是懒加载的,需要滚动到可见区域才会加载。既然firecrawl不行,有没有更强大的工具呢?请求AI支招,它推荐了Scrapeless,咱们来试试。通过一番注册与认证后,获得了免费试用额度。官方没有提供直接的MCP server,不过我把它的开发文档丢给AI,几分钟就搞定了。

通过Scrapeless解决上面图片问题的方式,主要是模拟了浏览器滚动后使图片可见,从而抓取到图片。当然我们还要将抓取的HTML中的占位符替换为最终图片:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
    /**
     * 修复懒加载图片,替换SVG占位符为真实图片URL
     *
     * 微信公众号使用懒加载,滚动前图片为SVG占位符,
     * 真实URL存储在 data-src 等属性中
     *
     * @param {Object} articleContent - Cheerio 选择器对象
     * @param {Object} $ - Cheerio 实例
     */
    fixLazyImages(articleContent, $) {
        // 查找文章中的所有图片标签
        const images = articleContent.find('img');
        let fixedCount = 0;

        // 遍历每个图片,检查并修复懒加载占位符
        images.each((_i, img) => {
            const $img = $(img);  // 包装为 jQuery 风格选择器
            const src = $img.attr('src') || '';

            // 检查是否是SVG占位符(懒加载标志)
            if (src.includes('data:image/svg+xml')) {
                // 尝试从常见的懒加载属性中获取真实图片URL
                const realSrc = $img.attr('data-src')
                    || $img.attr('data-original')
                    || $img.attr('data-lazy-src');

                if (realSrc) {
                    $img.attr('src', realSrc);
                    fixedCount++;
                    this.log(`  ✅ 修复图片: ${realSrc.substring(0, 80)}...`);
                } else {
                    // 如果没有找到真实URL,尝试从其他 data-* 属性中查找
                    const attrs = Object.keys($img.attr());
                    for (const attr of attrs) {
                        if (attr.startsWith('data-') && $img.attr(attr).startsWith('http')) {
                            $img.attr('src', $img.attr(attr));
                            fixedCount++;
                            this.log(`  ✅ 修复图片 (从${attr}): ${$img.attr(attr).substring(0, 80)}...`);
                            break;
                        }
                    }
                }
            }
        });

        if (fixedCount > 0) {
            this.log(`📸 共修复 ${fixedCount} 张图片`);
        } else {
            this.log('⚠️  未发现需要修复的懒加载图片');
        }
    }

使用Scrapeless抓取网页

我们这里使用的是现成的服务,我看了一下抓取一次网页的成本很低,一次请求几分钱。 Scrapeless抓取网页成本

现在抓取的内容已经可以正常显示了,还有一些元信息如文章发布时间等因为不在Content中,需要额外一点小处理即可。接下来我们需要将内容导入到Reader中。

2. 提取摘要及标签

上面我将抓取功能封装为一个 MCP server 了,现在要调用它并且将相关返回进一步处理。比如摘要,标签等通过 LLM 智能生成。考虑到作为 server 的可测试及调试性,我使用 GO 来实现它。代码已经不重要(若有人有需求未来可公开于 GitHub),我们看一下提示语即可:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
你是一名严谨的网页内容解析器。请从给定 HTML 中提取文章字段,并输出唯一一个 JSON 对象。除 JSON 外不要返回任何多余文字。

【任务目标】
- 从HTML提取适合Readwise Reader的字段,生成干净的article HTML与结构化元信息
- 不臆造:未知则置为null
- 保留内容语义与层级

【重要时间信息】
- 抓取时间:2024-05-10T14:30:00+08:00
- 当前年份:2024年(时区:CST +08:00)

【输入】
- 页面URL:https://example.com/article
- 元信息Metadata:
{
  "title": "示例标题",
  "author": "作者名"
}
- HTML内容:
<html>...</html>

【输出JSON字段定义】
{
  "url": string,
  "should_clean_html": boolean,
  "title": string|null,
  "author": string|null,
  "published_date": string|null,
  "image_url": string|null,
  "summary": string|null,
  "category": "article"|"email"|"rss"|"highlight"|"note"|"pdf"|"epub"|"tweet"|"video"|null,
  "tags": string[],
  "notes": string|null,
  "location": "new"|"later"|"archive"|"feed"|null,
  "saved_using": string|null
}

【提取规则】
1. url:使用输入的URL
2. should_clean_html:设为true,让API自动清理HTML
3. title:优先级:og:title > title标签 > h1标签 > 从内容推断
4. author:优先级:og:article:author > meta[name="author"] > byline文本 > 作者署名

6. published_date(重要):
   a) 优先查找meta时间:article:published_time、publishdate、date 等
   b) 在正文中匹配日期:'YYYY年MM月DD日'、'YYYY-MM-DD'、'MM月DD日HH:MM' 等;'今天/昨天/前天'等需换算
   c) 年份缺失时,使用当前年份:2024年
   d) 输出格式:严格ISO8601,且使用东八区(+08:00),如:YYYY-MM-DDTHH:MM:SS+08:00
   e) 若完全无时间信息,则置为null

7. image_url:优先级:og:image > twitter:image > 第一张内容图片(必须绝对URL)
8. summary:基于文章内容生成客观概述,2-4句话
9. category:按内容类型判断,通常为'article'
10. tags:从标题和内容中提取3-8个标签(字符串数组)
11. location:固定设为'new'
12. saved_using:固定设为'web_extractor'

【重要提醒】
- published_date:当出现'X月Y日Z:Z'时,年份必须使用当前年份;最终必须为+08:00的ISO8601
- 所有URL转换为绝对URL
- 不要臆造,不确定设为null
- 严禁在输出JSON中包含原始HTML或Markdown内容(不要输出 html 字段)

【输出要求】
- 仅返回合法JSON对象,字段与类型严格符合Readwise Reader API
- published_date为完整ISO8601(+08:00)或null
- 不要包含 html 字段;should_clean_html=true;location="new";saved_using="web_extractor"

最开始我犯了个错误,提取用时很长,仔细一看,它还返回了文章本体内容,这也难怪!千万别像我这样又费时又费钱,所以提示语中限制了某些字段不返回。上面我们让AI的输出完全按照Readwise API的文档来,这样便于接下来使用。

3. 将文章导入到Reader

Readwise 提供了 API 可以助于我们将现成的 HTML 导入到它 Reader 中,它的文档在这里:Readwise API。借助于 AI,我把文档链接丢给它,没一会儿它就完成了相关接口封装,这时我连文档都还没看完,这颇有点温酒斩华雄的感觉。如果你的 AI 不能正常读取到文档,前面提过的 firecrawl mcp 不失为一个好选择,我经常借用它来访问某些文档。

代码写好,单元测试也看起来一切正常,哐当~导入成功!我们去 Reader 中看一看,咦,怎么文章的封面是这个玩意?它提示微信不允许展示,而文章里面的图片一切正常了,这是为什么呢?(此处不放图了,文章我还想在微信公众号发出呢:D)

显然直接怀疑对象就是微信限制了其它来源的访问,不过咱们文章内部又能显示图片?直接F12分析,猜想是正确的。 Reader Referer

可是蹊跷的是:我使用方案一中第三方解决方案,它的图片能正常显示。我进一步分析了一下,它的封面图片域名和我的不一样,是 wework.qpic.cn,我推测它们是将封面图片推到另一个自己可控的地方(不受 Referer 限制),然后修改了原文章的图片 URL,这样我们就可以正常显示图片了。咱也不是不能效仿,不过这会有一定成本。

还有一个蹊跷的事,尽管我没处理封面图片,但在手机的 Reader 客户端,它显示是正常的,会自动使用第一张图片(Why?)作为封面。

如果仅是电脑上有此问题,要解决这个就有其它方法了。网上看关于此问题的相关文章: https://an.admirable.pro/wechat-platform-referer/,我们借助于浏览器插件 Referer Control,添加一个规则将访问微信域名的请求的Referer设置为https://mp.weixin.qq.com,刷新后就可以正常显示图片了。

4. 通过企微触发抓取

为了更容易将公众号文章分享后保存,方案一中使用转发到企业微信的某个账号,自动保存。我尝试自建了一个企业,创建了一个自建应用,可以接收输入的消息并通过 Webhook 交给后端处理。 给应用设置webhook回调

之后我们完善服务器处理相关消息的逻辑即可,这块涉及消息加解密直接使用现成的 GO 包比较好,AI 可能尝试自己裸写相关逻辑,纯增加调试的工作量了。完成之后,当我将一个链接发给企业微信这个应用时,它便触发了上面我们的抓取文章以及导入到 Reader。我以为就要搞定收工了,但发生了意外。当我想通过微信添加这个应用,发现不可能,这条路不通。之后我又创建一个成员账号等,通过某种 hack 可以通过微信将消息发送到内部,但又不提供 API 访问和处理这类消息。准确的说不是不提供,需要企业认证,并且还需要额外付费,它这个功能叫会话内容存档。我理解微信限制这块的道理,避免虚假信息(企业)对微信生态的影响。

这条路走得戛然而止,现在我只能企业内部使用了,复制链接并且贴到内部应用号上,才能完成我的文章收藏功能。

这是这篇文章拖了很久的一个原因,没找到突破口。我还想找一个更好的途径便于一键分享,如果你有方法欢迎留言告知于我,感谢。

5. 效果展示

通过以上工作,我们算是按自己的想法实现了文章的抓取和导入到 Reader。我们来看一下效果: 效果展示

可以看到针对文档生成了摘要和标签等,这相比于第三方的方案一定程度满足了自己的诉求,关键是咱是免费的呢!

后记

本文主体内容到这里就结束了,感谢阅读。上面相关的代码都已经在GitHub开源了,如果你觉得有诚意,请给我的文章点个赞或在看呀!感谢感谢!

上文文章提到 Readwise,它的用途不止于此,比如我尝试统一管理邮件,邮件作为每天处理的信息流之一也是不错的,避免我经常漏掉某些重要通知。悄悄告诉你,Readwise虽然是收费的,我朋友说还提供了发展中国家的优惠,发个邮件很快申请到了,50% OFF。至于推广链接啥的,我太懒就不留了:)

本文介绍的方法,不光适用于微信公众号,只要是一个网页都适用,平常我还会看知乎等平台的文章,但知乎是有登录态限制的,如何将知乎文章保存呢?如果你也有兴趣,我也有一些想法,欢迎联系我探讨一下。

欢迎关注我,说不定后续就会有你想要的内容到来。下回见!

我是个爱折腾技术的工程师,也乐于分享。欢迎点赞、关注、分享,更欢迎一起探讨技术问题,共同学习,共同进步。为了获得更及时的文章推送,欢迎关注我的公众号:爱折腾的风

扫码关注公众号