首页 / 资讯中心 / 文章详情

Scrapy多站点新闻爬虫实战:网易搜狐凤凰澎湃抓取与增量更新

Scrapy多站点新闻爬虫实战:网易搜狐凤凰澎湃抓取与增量更新 ★ FEATURED ARTICLE
简介这是一份基于Scrapy框架的Python新闻爬虫项目源码面向具备一定Python基础、希望学习分布式爬虫与数据采集的开发者。项目针对网易、搜狐、凤凰和澎湃四家新闻站点实现文章标题、正文、评论及发布时间的抓取与本地保存并支持定时运行以完成每日更新适合用作爬虫课程设计或数据采集练手案例。压缩包共26个文件以18个py脚本为核心涵盖spiders爬虫逻辑、pipelines数据管道、middlewares中间件与items字段定义另含3个xml配置、1个cfg及1个md说明文档整体约26KB结构清晰便于按模块阅读。资源已有495人学习下载。通过阅读源码可理解Scrapy从Scheduler调度、Downloader下载到Spider解析、Item Pipeline存储的完整流程并掌握多站点适配、评论抓取与定时更新等实用思路。1. 从四个门户站抓新闻这套 Scrapy 爬虫到底能扛什么活做舆情监控或者新闻聚合的朋友大概率都遇到过这个场景老板丢过来一句“把网易、搜狐、凤凰、澎湃的新闻每天扒下来标题正文评论时间都要”然后你打开四个网站一看结构完全不一样有的评论是异步加载有的正文藏在 iframe 里有的列表页翻着翻着就给你来个验证码。这时候如果还想着用 requests BeautifulSoup 一把梭基本就是给自己挖坑。这套基于 Scrapy 框架的 Python 新闻爬虫就是冲着这个场景来的——它把四个门户站的抓取逻辑拆成独立的 spider用 Scrapy 的调度器统一管理请求队列抓下来的标题、正文、评论、发布时间通过 Item Pipeline 落盘保存。适合谁用一是需要批量采集新闻语料做 NLP 训练或舆情分析的人二是想拿一个真实多站点爬虫项目练手 Scrapy 的开发者。它不解决“绕过所有反爬”这种玄学问题但能把多站点异构页面的抓取流程给你搭明白。2. Scrapy 的调度链路为什么这套架构能撑住 10 万页2.1 从 Scheduler 到 Pipeline 的数据流拆解很多人用 Scrapy 就是写个 spider 就完事但其实这套框架真正值钱的地方在于它把“请求调度”和“数据落地”做了解耦。整个链路是这样的初始 URL 进 SchedulerScheduler 按优先级和去重规则把请求交给 DownloaderDownloader 下载完页面丢给 Spider 解析。Spider 解析出来的结果分两路走——需要继续抓的链接通过 Middleware 传回 Scheduler 排队需要保存的数据封装成 Item 送进 Item Pipeline 做清洗和存储。这个设计对新闻爬虫来说特别关键。因为新闻站点有个特点列表页要翻很多页详情页要抓正文和评论评论还经常分页。如果不用 Scrapy 的调度机制你得自己维护一个待抓队列还得处理去重、重试、并发控制写到最后就是一个残缺版的 Scrapy。这套项目直接用了框架自带的调度器你只需要在 spider 里定义好start_urls和解析规则剩下的排队、去重、失败重试它帮你管了。项目结构里scrapyspider是框架主目录spiders放核心抓取逻辑debug_163.py、debug_sohu.py、debug_ifeng.py、debug_pengpai.py是四个站点的运行入口。这种拆法有个好处调试单个站点的时候不用动其他站的代码哪个站改版了就改对应的 spider 和 debug 文件互不影响。2.2 四个站点的 spider 拆分策略为什么不是一个 spider 打天下因为网易、搜狐、凤凰、澎湃的页面结构差异太大了。网易新闻的正文通常在div.post_body里评论走的是异步接口搜狐的正文有时候嵌在 iframe 里凤凰网的列表页和详情页 URL 规则不一样澎湃的页面相对规整但评论加载方式又不同。如果硬塞进一个 spider解析函数里全是 if-else 判断域名维护起来就是灾难。拆成四个 spider 之后每个 spider 只管自己的站点parse方法里写该站的 XPath 或 CSS 选择器parse_detail处理正文和评论。常见做法是在items.py里定义好统一的字段结构比如title、content、comments、publish_time、url、source这样不管哪个站抓下来的数据进 Pipeline 之后格式是一致的后面存 CSV 或者入库都不用再改。# items.py 定义统一的数据结构 import scrapy class NewsspiderItem(scrapy.Item): title scrapy.Field() # 新闻标题 content scrapy.Field() # 正文内容 comments scrapy.Field() # 评论列表 publish_time scrapy.Field() # 发布时间 url scrapy.Field() # 原文链接 source scrapy.Field() # 来源站点标识字段定义看着简单但这是多站点爬虫能统一落地的前提。source字段尤其重要后面做数据筛选或者分站点统计的时候全靠它。comments用 Field 存列表Pipeline 里再决定是拼成字符串还是拆成多条记录。2.3 评论抓取的两种常见路径新闻评论是这套项目里最容易翻车的部分。四个站点里有的评论直接渲染在 HTML 里有的走 XHR 异步接口有的需要滚动加载。Scrapy 本身不执行 JavaScript所以遇到异步加载的评论常见做法有两种一是找到评论的 API 接口直接用 Scrapy 发请求拿 JSON二是配合 Playwright 或 Selenium 做动态渲染。这个项目里debug_qq.py的存在说明评论抓取可能涉及额外的请求逻辑。我一般会先打开浏览器开发者工具切到 Network 面板看评论加载时有没有独立的 XHR 请求。如果有就把那个接口的 URL、请求头、参数抄下来在 spider 里用scrapy.Request单独发一次。注意接口通常需要带上Referer和User-Agent否则返回空数据。# 在 spider 中追加评论请求的典型写法 def parse_detail(self, response): item NewsspiderItem() item[title] response.xpath(//h1/text()).get() item[content] .join(response.xpath(//div[classpost_body]//text()).getall()) item[publish_time] response.xpath(//span[classtime]/text()).get() item[url] response.url item[source] netease # 构造评论接口请求 comment_api fhttps://example.com/comment/api?id{news_id}page1 yield scrapy.Request( comment_api, callbackself.parse_comments, meta{item: item}, headers{Referer: response.url} # 评论接口通常校验 Referer ) def parse_comments(self, response): item response.meta[item] data response.json() item[comments] [c[content] for c in data.get(comments, [])] yield item这里用meta把 item 从详情页传到评论解析函数是 Scrapy 里跨请求传递数据的标准做法。headers里补Referer是因为很多站点的评论接口会校验来源页不带就直接返回 403。评论分页的话在parse_comments里判断有没有下一页有就继续 yield 新的 Request直到抓完。3. 跑起来才算数环境配置与四站调试实操3.1 环境依赖与项目初始化拿到NewsSpider-master.zip之后先别急着跑。Scrapy 项目对环境有要求Python 版本建议 3.7 以上pip 装好之后先确认 Scrapy 能正常导入。项目里带了settings.pyc说明原始环境可能编译过但.pyc文件在不同 Python 版本下不一定兼容建议以settings.py为准。# 创建虚拟环境并安装依赖 python -m venv newsenv source newsenv/bin/activate # Windows 用 newsenv\Scripts\activate pip install scrapy pip install requests # 部分 debug 脚本可能用到 # 验证 Scrapy 安装 scrapy version装完之后进项目根目录看到scrapy.cfg就说明位置对了。scrapy.cfg里定义了 settings 模块的路径Scrapy 靠它找到项目配置。如果这个文件缺失或者路径写错运行时会报ModuleNotFoundError。# scrapy.cfg 典型内容 [settings] default scrapyspider.settings [deploy] project scrapyspiderdefault指向scrapyspider.settings意味着 Scrapy 会去scrapyspider/settings.py里读配置。如果你改了项目目录名这里必须同步改否则跑不起来。3.2 settings.py 里必须调的五个参数settings.py是 Scrapy 的行为控制中心默认配置能跑但效率不高做新闻批量抓取至少要调这几个参数默认值建议值作用DOWNLOAD_DELAY01~3请求间隔防止被封CONCURRENT_REQUESTS168~16全局并发数CONCURRENT_REQUESTS_PER_DOMAIN84~8单域名并发RETRY_TIMES23失败重试次数DOWNLOAD_TIMEOUT18030超时秒数DOWNLOAD_DELAY设成 0 的时候跑得飞快但四个门户站都有频率检测跑不了几百页就给你返回 403 或者验证码页。我一般设 1.5 秒起步观察一段时间没有异常再往下调。CONCURRENT_REQUESTS_PER_DOMAIN比全局并发更关键因为你是对同一批站点持续抓取单域名并发太高等于告诉对方“我在爬你”。# settings.py 关键配置片段 DOWNLOAD_DELAY 1.5 CONCURRENT_REQUESTS 16 CONCURRENT_REQUESTS_PER_DOMAIN 4 RETRY_TIMES 3 DOWNLOAD_TIMEOUT 30 # 开启 AutoThrottle 让 Scrapy 自动调节速率 AUTOTHROTTLE_ENABLED True AUTOTHROTTLE_START_DELAY 1 AUTOTHROTTLE_MAX_DELAY 10 AUTOTHROTTLE_TARGET_CONCURRENCY 2.0AUTOTHROTTLE是 Scrapy 自带的自动限速扩展它会根据服务器的响应时间动态调整请求间隔。开了这个之后DOWNLOAD_DELAY相当于一个下限实际间隔由 AutoThrottle 根据延迟反馈来定。对新闻站点这种响应时间波动大的目标比固定延迟更稳。3.3 四个 debug 脚本的运行方式项目里的debug_163.py、debug_sohu.py、debug_ifeng.py、debug_pengpai.py是四个站点的独立运行入口。这种设计的好处是你可以单独调试某个站不用把四个站都跑一遍。常见做法是在每个 debug 脚本里用CrawlerProcess启动对应的 spider# debug_163.py 典型结构 from scrapy.crawler import CrawlerProcess from scrapy.utils.project import get_project_settings from scrapyspider.spiders.netease import NeteaseSpider process CrawlerProcess(get_project_settings()) process.crawl(NeteaseSpider) process.start()get_project_settings()会自动读取scrapy.cfg里指定的 settings 模块所以不用手动指定配置文件。跑的时候直接python debug_163.py就行。注意 spider 的类名要和spiders目录下的实际类名对上改过代码之后如果报ImportError先检查导入路径。四个站建议逐个跑通再一起上。先跑网易看能不能正常抓到列表页和详情页再跑搜狐重点看 iframe 里的正文有没有抓到凤凰和澎湃类似。每个站跑通之后观察日志里的item_scraped_count确认数据确实进了 Pipeline。3.4 Pipeline 落盘与 csv_process.py 的角色抓下来的数据最终要落盘。Scrapy 默认支持Feed Exporter可以在 settings 里配置输出格式和路径但项目里单独写了pipelines.py和csv_process.py说明对数据做了额外处理。# pipelines.py 典型的数据清洗逻辑 import csv from datetime import datetime class NewsspiderPipeline: def open_spider(self, spider): self.file open(fnews_{spider.name}_{datetime.now().strftime(%Y%m%d)}.csv, w, newline, encodingutf-8) self.writer csv.writer(self.file) self.writer.writerow([title, content, comments, publish_time, url, source]) def process_item(self, item, spider): # 清洗正文中的多余空白 item[content] .join(item[content].split()) if item.get(content) else # 评论列表拼成字符串 item[comments] | .join(item[comments]) if item.get(comments) else self.writer.writerow([item.get(k, ) for k in [title, content, comments, publish_time, url, source]]) return item def close_spider(self, spider): self.file.close()open_spider在爬虫启动时打开文件并写表头process_item每收到一个 item 就写一行close_spider在结束时关闭文件。按 spider 名字和日期分文件避免多个站的数据混在一起。csv_process.py可能是对生成的 CSV 做二次处理比如合并多天数据、去重或者格式转换具体逻辑要看文件内容但思路一般是读入原始 CSV按 URL 去重再输出清洗后的版本。注意CSV 写入时newline不能省否则 Windows 下会出现空行。编码统一用utf-8不然中文会乱码。4. 避坑排查多站点爬虫最容易翻车的五个地方4.1 现象网易新闻正文抓下来是空的原因网易的正文容器 class 名改过版本旧代码里的 XPath 匹配不到。另外部分网易新闻的正文在div.post_body里但页面结构在不同频道下不一致。解决先用浏览器打开目标页面右键检查正文元素确认当前的 class 或 id。XPath 尽量用相对路径加多个备选比如//div[contains(class,post_body)]//text()contains比精确匹配更抗改版。如果还是空检查是不是被重定向到了移动版页面移动版的 DOM 结构完全不同。4.2 现象搜狐的正文抓不到返回的 HTML 里没有内容原因搜狐部分新闻页的正文嵌在 iframe 里Scrapy 默认只下载主文档不处理 iframe 内容。解决在开发者工具里找到 iframe 的src属性那是一个独立 URL用scrapy.Request单独请求它再在回调里解析正文。如果 iframe 的 src 是 JavaScript 动态生成的就需要上 Playwright 做渲染Scrapy 配合scrapy-playwright可以处理这种场景。4.3 现象跑了几百页之后开始返回 403 或验证码页原因请求频率过高触发了站点的反爬机制。即使设了DOWNLOAD_DELAY如果并发数太高单位时间内的请求量仍然超标。解决先把CONCURRENT_REQUESTS_PER_DOMAIN降到 2DOWNLOAD_DELAY提到 3 秒观察是否恢复。同时检查User-Agent是否每次请求都一样Scrapy 默认的 UA 字符串很容易被识别。在middlewares.py里加一个随机 UA 中间件每次请求从 UA 池里随机选一个。# middlewares.py 随机 UA 中间件 import random class RandomUserAgentMiddleware: def __init__(self): self.agents [ Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36, Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15, Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36, ] def process_request(self, request, spider): request.headers[User-Agent] random.choice(self.agents)写完中间件之后要在settings.py里启用DOWNLOADER_MIDDLEWARES {scrapyspider.middlewares.RandomUserAgentMiddleware: 543}。数字是优先级543 是常见位置比默认的 UserAgentMiddleware 靠后执行。4.4 现象评论数据抓下来是空的或者只有第一条原因评论接口需要分页请求只抓了第一页或者接口返回的 JSON 结构和预期不一致字段名对不上。解决先在浏览器里手动翻评论观察每次翻页时 Network 面板里新增的请求把 URL 模板和参数规律记下来。然后在parse_comments里根据返回数据里的total或has_more字段判断是否继续翻页。字段名对不上的话把接口返回的 JSON 打印出来看一眼别靠猜。4.5 现象定时任务跑完发现数据没更新原因Scrapy 的JOBDIR去重机制会记住已经抓过的 URL第二次跑的时候如果 URL 没变会被直接过滤掉导致新数据抓不到。解决如果要做每日增量更新不能依赖 Scrapy 的默认去重。常见做法是在 Pipeline 里根据publish_time做过滤只保留当天或最近一天的数据或者在启动前清空JOBDIR目录。另外检查DUPEFILTER_CLASS的配置默认的RFPDupeFilter是基于 URL 的如果新闻 URL 带时间戳参数去重可能失效。5. 增量更新与数据验证让爬虫每天自己跑起来5.1 用 cron 或计划任务实现每日抓取项目需求里写了“每个新闻网页及其评论能在 1 天内更新”这意味着爬虫需要定时运行。Linux 下用 cronWindows 下用任务计划程序。核心思路是每天固定时间启动一次爬虫跑完之后把数据追加到当天的文件里。# Linux crontab 示例每天凌晨 2 点跑四个站 0 2 * * * cd /path/to/NewsSpider-master /path/to/newsenv/bin/python debug_163.py logs/163.log 21 30 2 * * * cd /path/to/NewsSpider-master /path/to/newsenv/bin/python debug_sohu.py logs/sohu.log 21 0 3 * * * cd /path/to/NewsSpider-master /path/to/newsenv/bin/python debug_ifeng.py logs/ifeng.log 21 30 3 * * * cd /path/to/NewsSpider-master /path/to/newsenv/bin/python debug_pengpai.py logs/pengpai.log 21四个站错开时间跑避免同时占用带宽和触发风控。日志重定向到文件方便排查21把错误输出也写进去。跑之前确认虚拟环境的 Python 路径写对了cron 的环境变量和登录 shell 不一样用绝对路径最稳。5.2 数据完整性校验的三种手段爬虫跑完不等于数据没问题。我一般会做三层校验第一层看日志里的item_scraped_count和预期数量对比差太多说明有页面没抓到第二层抽查 CSV 文件看标题、正文、时间字段有没有大面积空值第三层用脚本统计每个站点的数据量和字段完整率。# 数据校验脚本统计各站点数据量和空值率 import csv from collections import defaultdict stats defaultdict(lambda: {total: 0, empty_content: 0, empty_time: 0}) with open(news_netease_20250101.csv, r, encodingutf-8) as f: reader csv.DictReader(f) for row in reader: source row[source] stats[source][total] 1 if not row[content].strip(): stats[source][empty_content] 1 if not row[publish_time].strip(): stats[source][empty_time] 1 for source, s in stats.items(): print(f{source}: 总数{s[total]}, 正文空值{s[empty_content]}, 时间空值{s[empty_time]})这个脚本按source字段分组统计能快速看出哪个站的数据质量有问题。如果某个站的正文空值率超过 10%基本可以确定是 XPath 失效或者页面结构变了需要回去改 spider。5.3 一个我踩过的坑时区问题导致时间字段错乱有次跑完发现所有新闻的publish_time都比实际时间早了 8 小时。排查了半天以为是 XPath 抓错了后来发现是服务器时区设成了 UTC而新闻页面上显示的是北京时间。Scrapy 本身不处理时区转换抓下来是什么就是什么。如果页面上的时间是“2025-01-01 10:30”这种不带时区的字符串存进 CSV 之后做时间分析就会出错。从那以后我每次处理时间字段都强制走一遍时区确认先看服务器date命令输出再看目标页面时间的标注方式如果页面用的是北京时间而服务器是 UTC就在 Pipeline 里统一加 8 小时再存。这个习惯帮我省了不少后悔药。希望帮到你。本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?
咨询建站