1. 新闻流量入口的页面结构认知:目标网站选择与信息架构分析
先做个基本共识。做新闻类网站的爬虫项目,绝对不是把HTML抓下来、用正则抠几个标签就完事,这套老思路在今天已经很难落地。新闻网站是所有垂直行业站里页面结构最不规整、更新频率最高、同类页面模板变体最多的一类目标。
我在做这个项目的时候,最初被问的第一个问题是:为什么要拿新闻网站来练手?原因其实很直接——新闻页的正文结构具有极强的重复性,但同时又有大量的“页面级别噪声”。比如推荐阅读、热门排行、评论热帖、相关链接、广告植入、图片附件、作者简介、版权声明,这些块在每个新闻页里都存在但位置不固定。这种“结构相似但不完全一致”的特征,正好是锻炼正文提取和文本去噪的好场景。
而 TF-IDF 和 TextRank 这两个算法选型在这类场景里非常合适。TF-IDF 做的是“基于统计的主题词抓取”,不依赖外部知识库;TextRank 是“基于图模型的句子重要性排序”,同样不需要语料库训练。两个算法都属于无监督文本挖掘手段,不需要标注数据,跑起来快,效果可解释,适合作为新闻关键词抽取和摘要生成的第一版方案。
先明确信息架构。新闻站点普遍具备四级结构:首页、栏目页、列表页、详情页。栏目页负责展示某类新闻的标题列表,列表项往往只有标题、时间和摘要,详情页才是完整正文所在。这个结构意味着爬虫至少需要两跳:第一跳从栏目页拿详情页URL,第二跳从详情页提取正文。
需要注意的一个细节是:很多新闻详情页会把完整正文放在<div id="content">或者<article>标签内,但栏目页列表的分页逻辑并不统一。有的站用纯静态链接,有的站是JS动态加载,有的站是无限滚动。这些都会直接影响采集方案选型,我不建议一上来就上Selenium,静态页能解决的绝不用浏览器渲染,成本完全不在一个量级。
以最常见的静态栏目页为例,URL模式通常形如/list/1.html、/list/2.html。这个分页模式可以先拿到,再逐页解析列表里的<a>标签。项目和标题聚焦的链路就是“列表页采集 -> 正文页抓取 -> 文本清洗 -> 关键词抽取 -> 摘要生成”这五单,后面所有的内容都围绕这个主链路展开。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 请求策略里的细节:从UA伪装到延迟控制,爬虫与风控的软对抗
2.1 requests 还是 scrapy:轻量采集下的选型判断
调型阶段我用的requests+BeautifulSoup的组合,没有直接上scrapy。很多新手容易被“scrapy性能高、分布式能力强”这个标签带着走,但在一个以算法展示为核心目标的练手项目里,scrapy的调度器、中间件、Twisted异步模型反而会分散注意力。requests的同步请求模型足够直观,出了错也好定位。
还有一点,scrapy的XPath选择器确实好用,但BeautifulSoup的find()系列对新手更友好,排查标签层级关系时可以快速打印soup.prettify()。等后续需要扩大采集规模,再从requests切换到scrapy,核心的业务解析代码可以直接复用。
2.2 请求头的完整伪装:不是只有UA那么简单
这一块是整个采集环节里最容易出问题的地方。先说一个常见的反例:只设置User-Agent为Chrome字符串就去请求,结果被服务器返回403。
问题不在UA本身,而在请求特征组合。正常的浏览器请求会同时携带Accept、Accept-Language、Accept-Encoding、Referer、Connection等头部,服务端风控引擎会综合判断请求指纹。UA是一个维度,Referer和Cookie是另外两个高频检查项。
我实践中比较稳妥的一套头部配置是这样:
python复制headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 "
"(KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36",
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,"
"image/webp,*/*;q=0.8",
"Accept-Language": "zh-CN,zh;q=0.9,en;q=0.8",
"Accept-Encoding": "gzip, deflate, br",
"Referer": "https://news.example.com/",
"Connection": "keep-alive",
"Upgrade-Insecure-Requests": "1",
}
关键点:
Accept-Encoding不要自己去搞,让requests自动处理。如果手动写了gzip, deflate,收到响应后必须手动response.content解压,否则乱码问题排查起来很耽误时间。我在初版代码里踩过这个坑,后来干脆把这行去掉,交给requests的decompress逻辑。
UA池不是必需的,但必须要准备两三个备用的。不同新闻站的UA校验策略差异很大,有的只认Chrome最近两个大版本,有的对PhantomJS特征做了黑名单,有的对无头浏览器指纹有检测。一个UA被拉黑时,先换UA重试一次再上延迟策略,这是一条成本最低的试探路径。
2.3 频率控制:公平采集的底线,也是技术参数的必修课
请求间隔这个参数多少合适?我见过很多网上的公开代码直接写time.sleep(1),这个值说实话偏激进。大型新闻网站的单机并发承载能力很强,但风控引擎更关注的是“单一IP的单位时间请求数”和“请求路径的规律性”。
我自己的参数经验是这样:
| 场景 | 请求间隔 | 说明 |
|---|---|---|
| 栏目列表页 | 2~3秒 | 列表页请求量小,但URL规律性强,间隔太短容易被识别为脚本 |
| 详情页 | 3~5秒 | 正文页频率最高,建议随机抖动,不要固定间隔 |
| 失败重试 | 等指数退避 | 首次失败等30秒,第二次等60秒,最多重试3次 |
这里有一个容易被忽略的技术细节:请求间隔的随机抖动很关键。如果你每个请求都是精确的3秒,风控模型反而更容易识别出机器行为,因为人的操作节奏一定有波动。我当时是用random.uniform(2.5, 4.5)来模拟这种波动。
还要处理重定向和响应状态码。allow_redirects=True是默认行为,但有些新闻详情页会做302跳转到登录页或验证页,这需要检查最终URL和响应内容长度。我习惯在每次下载后判断response.status_code == 200且len(response.text) > 500,否则记录异常URL,统一丢进待重试队列。
2.4 采集完成后的数据存储格式选择
新闻采集的原始数据建议保留HTML和解析后的结构化字段两份。解析后的字段用JSON存储比较方便后续算法读取,HTML原文保留在本地则方便回溯编码问题、标签结构变化。
json复制{
"url": "http://news.example.com/politics/2024/1122.html",
"title": "文章标题",
"publish_time": "2024-11-22 10:30:00",
"author": "作者名",
"content_text": "正文纯文本内容",
"raw_html": "归档用,可省略",
"crawl_time": "2024-11-22 11:00:00"
}
每个字段都有存在的价值:publish_time在做时间衰减加权时有用,author可以做聚类分析,content_text就是后面的算法输入。这个文件结构从项目第
