ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

公众号文章批量抓取与静态博客搭建实战

公众号文章批量抓取与静态博客搭建实战 1. 从翻历史消息翻到手指抽筋说起公众号文章有个很别扭的地方你明明记得某天看过一篇特别有用的干货但想再找回来的时候只能在那条对话里往上滑滑到天荒地老。微信自带的搜索功能对公众号历史文章的覆盖也不算友好尤其是那些更新频率高、内容量大的号翻起来简直是体力活。我这个需求的起点很朴素我关注了几个更新很勤的公众号每天推送好几条时间一长想找某篇旧文基本靠缘分。试过在聊天记录里搜关键词结果要么搜不到要么搜出来一堆不相关的。后来我想与其每次都在微信里受罪不如把这些文章搬出来做成一个自己能掌控的博客想按日期查就按日期查想按关键词搜就按关键词搜一秒定位。这篇就聊聊我具体是怎么搭的中间踩了哪些坑以及如果你也想干同样的事哪些环节可以省事、哪些环节千万别偷懒。整套方案的核心链路其实就四步批量获取文章链接、抓取正文内容、转成结构化数据、生成静态博客。听起来简单但每一步都有细节能把人卡住尤其是批量下载和格式转换这两块。先说清楚这套东西适合谁如果你只是偶尔想存几篇文章直接用微信的收藏就够了没必要折腾但如果你像我一样关注的号多、历史文章存量大、又希望有一个统一的检索入口那自己搭一个博客确实是最舒服的方案。下面按实际搭建顺序展开。2. 批量拿到历史文章链接别一上来就写脚本2.1 先搞清楚你能拿到什么数据动手之前得先明确一件事公众号文章的公开访问入口是文章链接而历史文章的列表并不是随便就能完整拉到的。我的做法是先从自己能接触到的入口把链接收集起来比如从公众号的推送记录、分享出来的文章链接、以及一些公开的文章聚合页面。这一步的目标只有一个——拿到尽可能全的文章URL清单正文内容后面再抓。这里有个经验不要指望一次性拿到全部历史文章。很多号的文章跨度好几年链接散落在各处。我的策略是分批收集先拿到最近半年的跑通整条链路再回头补历史数据。这样你能快速验证方案可行性而不是卡在数据收集阶段就放弃了。收集链接的时候我建议直接存成一个纯文本文件一行一个URL命名成urls.txt。别小看这个格式选择后面写脚本读取的时候纯文本是最省事的不用解析JSON也不用管编码问题记得统一存成UTF-8。2.2 用脚本批量下载而不是手动复制手动复制链接这件事超过二十条就会让人崩溃。这时候脚本就派上用场了。我用的是最基础的Shell脚本配合循环逻辑很简单读一行URL请求一次把返回的HTML存下来文件名用序号或者文章标题。#!/bin/bash i1 while read -r url; do echo 正在抓取第 $i 篇: $url curl -s -A Mozilla/5.0 $url -o raw/${i}.html i$((i1)) sleep 2 done urls.txt这段脚本里有几个关键点值得说。-A参数是设置请求头里的User-Agent不加的话有些页面会返回空内容或者被拦截。sleep 2是刻意加的延迟别嫌慢批量请求如果太密集很容易触发对方的访问限制到时候整批都拿不到反而更亏。我一开始图快把sleep去掉结果跑到三十多条就开始大量失败加了延迟之后稳定多了。提示抓取频率一定要克制。这不是技术问题是基本的礼貌问题也是保证你自己能长期稳定拿到数据的前提。2.3 文件名别用标题用日期加序号我踩过一个坑一开始想用文章标题当文件名结果标题里带各种特殊字符——斜杠、问号、冒号、emoji在文件系统里全是非法字符脚本直接报错。后来改成日期_序号.html的格式比如20240315_001.html世界一下子清净了。日期从哪来如果URL里带日期信息最好没有的话就在抓取的时候从HTML里解析发布时间。实在解析不出来就按抓取顺序编号后面人工补日期也行。文件名规范这件事看着小但当你手上有几千个文件的时候一个统一的命名规则能救命。3. 把HTML变成能读的正文解析比抓取更磨人3.1 为什么不能直接存HTML就完事有人可能觉得HTML存下来不就能看了吗能看但不好用。公众号文章的HTML里塞满了各种样式、脚本、广告位、推荐阅读模块你打开一个文件真正想看的那几百字正文可能被埋在一堆无关内容里。而且你要做的是博客需要的是干净的正文内容不是原始页面。所以解析这一步的目标很明确从HTML里把标题、正文、发布时间、作者这几个字段抽出来其他全部丢掉。这一步用Python做最顺手BeautifulSoup或者lxml都行。3.2 定位正文的那几个选择器公众号文章的正文通常在一个固定的容器里常见的是#js_content这个id。但不同时期、不同模板的文章结构可能不一样所以不能死磕一个选择器。我的做法是写一个优先级列表挨个尝试from bs4 import BeautifulSoup def extract_content(html): soup BeautifulSoup(html, lxml) # 按优先级尝试多个选择器 for selector in [#js_content, .rich_media_content, article]: node soup.select_one(selector) if node and len(node.get_text(stripTrue)) 100: return node return None这里加了个长度判断 100是为了避免选到一个空的或者只有几个字的容器。实测下来这个兜底逻辑能覆盖绝大多数情况。如果三个选择器都没命中那篇文章就标记为解析失败单独放到一个列表里后面人工处理不要让整个批处理因为一篇失败就中断。3.3 图片和代码块的处理正文里的图片是个麻烦事。公众号的图片是外链直接引用的话哪天对方图床挂了你的博客就全是裂图。我的处理方式是把图片也下载到本地存到images/目录然后把正文里的img标签的src替换成本地路径。这样博客就是完全自包含的不依赖任何外部资源。代码块相对好办公众号的代码块在HTML里通常有特定的样式类解析的时候保留pre和code标签的内容就行。但要注意有些代码块里的换行是用br实现的转成Markdown的时候得把br换成真正的换行符否则代码会挤成一行完全没法看。3.4 转成Markdown还是直接存HTML这是个值得纠结的问题。存HTML的好处是保留原始排版坏处是文件大、不好编辑、后续处理麻烦。转Markdown的好处是干净、通用、方便二次加工坏处是转换过程会丢失一些复杂排版。我最后选了Markdown理由是我要的是一个能长期维护、方便检索的知识库不是原页面的备份。Markdown转出来之后我还能用各种工具做全文搜索、生成目录、导出PDF。转换用的库是html2text配置里把body_width设成0不自动换行ignore_links设成False保留链接基本就够用了。4. 用Excel做中间层一个被低估的整理工具4.1 为什么中间要过一道Excel你可能会问抓完直接生成博客不就行了为什么要用Excel我的理由是Excel是一个极好的人工校对层。自动抓取难免有错——标题抓错了、日期解析错了、正文抓空了这些问题在生成博客之前如果不发现后面就是一堆脏数据。把每篇文章的元数据标题、日期、作者、URL、正文文件路径、解析状态写进一个Excel表格我可以快速扫一眼把明显有问题的行标出来手动修。这个环节花十分钟能省掉后面几个小时的返工。用Python写Excel很简单openpyxl或者pandas都行。我习惯用pandas因为后面做统计和筛选方便import pandas as pd data [] for item in parsed_items: data.append({ title: item[title], date: item[date], author: item[author], url: item[url], content_path: item[path], status: item[status] }) df pd.DataFrame(data) df.to_excel(articles.xlsx, indexFalse)4.2 表格里该放哪些列列的设计直接决定了这个表格好不好用。我最后定下来的列是title标题、date发布日期、author作者、url原文链接、content_path正文Markdown文件路径、status解析状态、tags人工打的标签。status这一列特别有用我用三个值ok表示解析正常partial表示正文抓到了但可能不完整failed表示完全没抓到。筛一下failed的行集中处理效率很高。tags列是我手动加的因为自动打标签的准确率实在感人。手动打虽然累但打完之后检索体验完全不一样。我一般按主题打比如工具方法论案例这种粗粒度标签够用就行不用太细。4.3 Excel和Markdown表格的互转这里顺带说一个高频需求有时候我需要把Excel里的数据转成Markdown表格贴到博客里或者反过来把Markdown表格转回Excel。手动转太蠢了我写了个小脚本处理。Excel转Markdown用pandas的to_markdown()就行需要装tabulatedf.to_markdown(table.md, indexFalse)Markdown转Excel稍微麻烦点得先解析表格结构。简单的做法是按行读用|分割去掉首尾的空格和分隔线然后塞进DataFrame。这个转换不常用但偶尔需要的时候有个现成脚本能省不少事。5. 生成静态博客选对工具半小时上线5.1 静态博客生成器的选择到了生成博客这一步选择就多了。Hugo、Hexo、Jekyll、VuePress 都能干这活。我选的是Hugo理由很直接编译速度快。我手上有几千篇文章用Hexo编译一次要等好几分钟Hugo几秒钟就完事。对于需要频繁重新生成整个站点的场景这个速度差异是决定性的。Hugo的目录结构很清晰content/放Markdown文章layouts/放模板static/放静态资源config.toml放配置。我要做的就是把解析出来的Markdown文件按Hugo要求的格式放进content/posts/目录每篇文章的头部加上front matter就是那几行---包起来的元数据。5.2 Front matter怎么写Hugo的文章头部需要元数据格式是YAML--- title: 文章标题 date: 2024-03-15 author: 作者名 tags: [工具, 方法论] source_url: 原文链接 ---这里有个细节date字段的格式必须是Hugo能识别的我统一用YYYY-MM-DD最省心。source_url是我自己加的字段用来在文章底部显示原文链接方便回溯。写front matter的时候要注意转义。标题里如果有双引号得转义成\否则YAML解析会报错。我一开始没注意这个结果有几篇文章标题带引号整个站点生成直接失败排查了半天才发现是这个小问题。5.3 按日期归档和全文搜索博客搭好之后最核心的两个功能就是按日期浏览和全文搜索。按日期归档Hugo自带支持配置里打开archives就行它会自动按年月生成归档页面。我还在首页加了一个日期选择器点某一天就跳到那天的文章列表这就是标题里说的想看哪天的文章一秒就能找到。全文搜索稍微费点事。静态博客没有后端搜索得靠前端实现。我用的是lunr.js思路是在生成站点的时候把所有文章的纯文本内容导出成一个JSON索引文件前端加载这个索引用户输入关键词的时候在本地做匹配。几千篇文章的索引文件大概几MB首次加载稍慢但之后搜索是瞬时的。注意索引文件别把全文都塞进去只放标题和摘要就够了否则文件会大到影响加载速度。需要看全文的时候点进文章就行。5.4 部署和更新静态博客的部署是最省心的部分。生成出来的public/目录就是一堆静态文件扔到任何能托管静态资源的地方都能跑。我用的是对象存储加CDN成本几乎可以忽略不计。更新流程也很简单新文章抓取解析完放进content/posts/跑一次hugo把public/同步上去完事。我写了个一键脚本把这几步串起来新增文章之后执行一次几十秒就能在线上看到。6. 那些让我熬夜的坑以及怎么绕过去6.1 编码问题中文乱码的根源抓取和解析过程中最容易遇到的就是编码问题。有些页面的HTML声明是GBK有些是UTF-8还有的声明和实际不符。如果处理不当中文全是乱码。我的处理方式是不信任HTML里的声明直接用chardet检测实际编码然后统一转成UTF-8。这一步在解析之前做能避免后面所有的乱码问题。import chardet def decode_html(raw_bytes): detected chardet.detect(raw_bytes) encoding detected[encoding] or utf-8 return raw_bytes.decode(encoding, errorsreplace)errorsreplace是兜底遇到实在解不出来的字符就用占位符代替保证程序不崩。个别字符乱码可以接受整个文件读不出来才是灾难。6.2 脚本闪退和命令找不到在Windows上跑脚本的时候我遇到过两个典型问题。一个是脚本命令闪退双击运行窗口一闪就没了根本看不到报错。解决办法是在脚本末尾加pause或者在命令行里手动执行这样报错信息能留住。另一个是npm不是可运行的程序这类报错本质是环境变量没配好。装完Node.js之后npm的路径没加到PATH里命令行就找不到。这个问题的排查思路很简单先确认装没装再确认PATH里有没有最后确认版本对不对。别一上来就重装先看PATH。6.3 批量下载的失败重试批量下载的时候总有一定比例会失败——网络抖动、对方限流、页面结构变了各种原因都有。我的做法是给每个下载任务记录状态失败的单独存一个failed.txt跑完一轮之后对失败列表重试重试还是失败的再人工看。重试的时候把延迟调大一点比如从2秒加到5秒成功率会明显提升。这个策略我用了很久基本上两轮之后失败率就能降到很低。6.4 正文抓空的几种情况正文抓空是最让人头疼的因为你不看不知道一看发现几十篇都是空的。常见原因有几个一是页面结构变了选择器没命中二是文章是纯图片形式发的没有文字正文三是文章被删了页面返回的是错误页。针对第一种我的选择器列表里加了兜底逻辑命中不了就标记failed不硬抓。针对第二种纯图片文章我单独处理把图片下载下来正文就放图片。针对第三种检查返回内容里有没有该内容已被发布者删除之类的关键词有的话直接标记为deleted不浪费时间。7. 几个让整套流程更顺手的补充技巧7.1 用标签和全文搜索配合光有全文搜索还不够因为搜索是精确匹配你搜下载可能搜不到讲抓取的文章。标签的作用就是补这个短板。我给每篇文章打两三个粗标签搜索的时候可以按标签筛选再在筛选结果里搜关键词命中率高很多。标签体系别搞太复杂我一开始设计了十几个标签结果自己都记不住哪个是哪个。后来精简到六七个反而用得更顺。7.2 定期备份原始数据抓取和解析的结果一定要备份尤其是那个Excel表格和Markdown文件。我吃过亏有一次误操作把content/目录清空了幸好Excel表格还在重新生成一遍就行。如果连表格都没了那就得从头抓那才叫绝望。备份策略很简单每周把content/、articles.xlsx、urls.txt打包存一份到另一个地方。不用搞什么复杂的增量备份全量打包就行反正体积不大。7.3 导出PDF作为离线备份有时候我需要把某几篇文章导出成PDF方便离线看或者分享给别人。Markdown转PDF用pandoc最方便pandoc article.md -o article.pdf --pdf-enginexelatex -V mainfontNoto Sans CJK SC关键是mainfont要指定一个支持中文的字体否则中文全是方块。这个坑我踩过导出来的PDF中文全变成豆腐块排查了半天才发现是字体问题。7.4 关于自动化程度的取舍整套流程我并没有做成全自动。抓取和解析是自动的但打标签、校对标题、处理失败项这些环节我保留了手动。原因很简单全自动的准确率达不到我的要求与其花时间调自动化不如手动处理那百分之几的异常总时间反而更少。这个取舍因人而异。如果你只是想要一个能看的备份全自动没问题如果你像我一样把这个博客当成日常检索工具那手动校对这一步值得保留。8. 我实际用下来的几点体会这套东西搭好到现在用了大半年最大的感受是前期在数据清洗上多花的每一分钟后期都能省回来。我一开始图快解析完直接生成博客结果站上一堆乱码标题和空文章后来不得不回头重新清洗反而更费时间。另一个体会是工具选型别追求新潮追求稳定。Hugo不是最时髦的但它的编译速度和稳定性让我省了很多心。同理脚本用最基础的Shell和Python就够了不需要上什么复杂的框架。最后说个实际的这个博客最大的价值不是存了多少文章而是找文章有多快。我现在想找某篇旧文打开博客输入关键词或者点日期几秒钟就定位到了。这个体验是微信里翻历史消息完全比不了的。如果你也有类似的需求按上面这套流程走一遍一个周末基本就能跑通。
返回列表