资讯动态

网络爬虫主流思路与反爬破解技术实战指南

发布时间:2026/9/10 1:49:15 来源:尧图企业网站定制
爬虫这个事说难确实难说简单也真简单。我见过不少新人一上来就抱着Scrapy啃半天连Requests的基础请求都写不利索最后卡在登录和验证码上直接放弃。也见过一些老手哪怕面对再复杂的站点脑子里始终就是那一套思路构造请求、解析响应、存储数据所有反爬破解技术本质上都是在为这三步扫清障碍。这篇文章就基于我这些年实际踩坑和实操的经验把网络爬虫的主流思路和反爬破解技术从头到尾捋一遍目标是让刚入门的新手能快速建立起完整的认知框架知道遇到一个网站该从哪里下手遇到反爬该往哪个方向想而不是背一堆散装的代码片段。1. 爬虫项目的核心设计思路与技术选型1.1 先搞清楚爬虫的本质三个环节一个循环很多新手会把爬虫想得很玄乎其实爬虫的本质就是一个模拟浏览器行为的数据获取过程拆开来看就是三个环节请求、解析、存储。请求环节解决的是“怎么把数据拿回来”解析环节解决的是“怎么从拿回来的内容中提取有用的部分”存储环节解决的是“提取出来的数据往哪里放”。三个环节构成一个完整链路爬虫项目的所有技术选型和架构设计都是围绕这三个环节展开的。以我在实际项目里的做法为例拿到一个采集需求后我做的第一件事永远不是写代码而是先在浏览器里手动打开目标网站按F12打开开发者工具切到Network面板刷新页面观察网络请求列表。这一步非常关键它能告诉你这个站点的数据是直接在HTML里返回的还是通过异步接口返回的JSON又或者是经过JS渲染后才出现在页面上的。这三种情况对应的技术方案完全不同HTML直出就只需要Requests加解析库异步接口就需要直接调接口拿JSONJS渲染就得考虑用浏览器自动化工具。判断逻辑补全之后才开始选型。我从不在项目刚开始时就引入复杂的框架而是遵循“先简单后复杂”的原则。能用Requests解决的就用Requests单机单线程跑不动了再加并发需要大规模采集了再上Scrapy需要分布式了再引入Scrapy-Redis。这个顺序很重要很多新手一上来就把架构堆得很高结果大部分功能根本用不上反而被框架本身的复杂度拖累了学习进度。1.2 为什么说Requests是爬虫的基本功而Scrapy是进阶武器Requests和Scrapy是爬虫领域最核心的两个Python库/框架但它们解决的层级完全不同。Requests是HTTP客户端库负责替你封装HTTP请求的构造和响应的接收帮你搞定URL拼接、Headers设置、Cookie传递、超时处理这些底层的细节。Scrapy则是完整的爬虫框架它除了包含请求功能之外还提供了调度器、下载器、爬虫中间件、实体管道、数据去重等等一整条数据处理流水线。我个人的建议是新手前期老老实实用Requests把请求、解析、存储这条链路跑通先建立对爬虫全流程的直觉。当你发现需要维护多个爬虫脚本、需要处理大量的去重和调度逻辑、需要把采集任务扩展成定时任务时再切换到Scrapy你会发现Scrapy的这些功能刚好打在痛点上。反之如果一上来就学Scrapy你会被它的目录结构、配置项、中间件机制搞得一头雾水因为你还没建立起“请求-解析-存储”这个基础认知不知道框架底层在替你做什么。这里有必要说一个我特别认同的观点爬虫技术栈的核心不是某个工具而是对HTTP协议的理解和对网页结构的分析能力。Requests和Scrapy只是工具工具可以换但底层思路是一致的。2. 请求环节实战Headers、Cookie、Session与并发策略2.1 请求头伪装决定你能否拿到数据的第一个门槛爬虫发起的请求和浏览器发起的请求在服务器看来最直观的区别就是Headers。最常见的反爬检测就是校验User-Agent和Referer。UA用来标识客户端的类型和版本正常浏览器会发一个完整的UA字符串比如Mozilla/5.0开头那段而代码默认的UA往往是python-requests/2.x。服务器一看UA就知道这不是浏览器直接拒绝或者返回验证码。我在项目中处理UA有两种方式静态写死一个浏览器UA或者维护一个UA池随机切换。前者适合小规模采集后者适合并发量较大的场景。UA池的思路很简单就是准备一个列表里面放几十个真实浏览器的UA每次发请求时随机取一个。这个操作看似简单但能有效规避某些网站针对单一UA的统计和限流。Headers里同样重要的还有Referer这个字段告诉服务器“我是从哪个页面跳转过来的”。很多网站的图片接口和API接口会校验这个字段Referer不对直接返回403。我自己就遇到过这种情况采集某个社交平台的头像图片时直接用图片URL请求死活返回403加上Referer之后瞬间就通透了。除了UA和Referer还有一些Requests库的默认行为会暴露爬虫身份。比如Requests默认的Accept-Encoding是gzip, deflate而浏览器会带上brBrotli格式Requests的Accept头是*/*浏览器是一长串的MIME类型列表。虽然这两个字段对大多数网站不构成影响但在面对较严格的防护时最稳妥的做法是直接把浏览器复制出来的完整Headers全部粘贴进代码里。2.2 Cookie与Session机制登录态和会话保持的关键HTTP协议本身是无状态的服务器怎么知道你是同一个人就是靠Cookie。服务器在响应头里通过Set-Cookie字段下发一个标识浏览器存下来后续每个请求都自动带上服务器靠这个识别身份。爬虫处理Cookie有两种场景。一种是无登录态的采集这时候需要关注的是服务器在第一次请求时下发的临时Cookie比如一些站点会先下发一个sessionid后续请求必须带上它才能正常访问。这种情况就用requests.Session它能自动帮你保存和携带Cookie省去手动维护的麻烦。另一种是需要登录态的采集比如爬取需要账号才能看到的数据。最简单的方法是手动登录后从浏览器开发者工具里复制Cookie字符串硬编码到代码里。这种方法适合短期小规模采集缺点是Cookie会过期过期后需要重新手动复制。更优雅的方案是用requests库配合登录接口做一次表单提交拿到登录Cookie后自动维持会话。具体做法是先分析网站的登录请求看是POST表单、JSON还是需要加签名的参数然后模拟提交账号密码。这里往往还会伴随验证码问题后面我会单独讲。2.3 超时、重试和并发请求稳定性的三大件新手最容易犯的一个错误是发请求时不设置超时时间。一旦目标站点响应慢或者TCP连接被卡住你的爬虫就会一直挂在那里整个任务都停摆。所以每个请求必须设置timeout参数我通常设成5秒连接超时加10秒读取超时。超时后配合重试逻辑比如连续失败3次则放弃当前URL记录日志后继续下一个。并发方面我建议新手不要在前期过度追求高并发。爬虫的瓶颈往往不在你的代码而在目标网站的服务能力和防护策略。我常用的一种方式是控制每秒钟的请求数QPS在1到3之间加上随机延时。这样既能保证采集速度又能显著降低被识别和封IP的概率。Requests本身是同步阻塞的如果需要并发可以用concurrent.futures的ThreadPoolExecutor或者用asyncio加aiohttp。但我个人建议在任务量没有大到一台机器跑不动之前老老实实单线程加延时是最省心的方案。2.4 一个完整的请求封装模板可直接复制我把自己常用的请求封装成一个函数统一设置UA、超时、重试和延时逻辑贴出来给你参考import requests import time import random from requests.adapters import HTTPAdapter UA_LIST [ Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36, Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36, ] def fetch_url(url, sessionNone, refererNone, retries3): sess session if session else requests.Session() sess.mount(https://, HTTPAdapter(max_retriesretries)) sess.mount(http://, HTTPAdapter(max_retriesretries)) headers { User-Agent: random.choice(UA_LIST), Accept: text/html,application/xhtmlxml,application/xml;q0.9,image/avif,image/webp,*/*;q0.8, Accept-Language: zh-CN,zh;q0.9,en;q0.8, } if referer: headers[Referer] referer try: resp sess.get(url, headersheaders, timeout(5, 10)) resp.raise_for_status() # 通过编码声明来避免乱码 if resp.encoding is None or resp.encoding ISO-8859-1: resp.encoding resp.apparent_encoding return resp except requests.RequestException as e: print(f请求失败: {url}, 错误: {e}) return None # 使用示例 resp fetch_url(https://example.com/page/1, refererhttps://example.com/) if resp: html resp.text这里有几个细节要提醒你HTTPAdapter的max_retries参数能自动处理连接层的重试配合requests默认的异常处理比较省事apparent_encoding是从内容里猜测编码对于没有声明编码的站点非常有用否则你解析出来的中文字符全是乱码。别小看这个函数它几乎是我所有爬虫脚本的地基脏活累活都在这里干完了。提示不要在生产环境里把超时设得过大否则批量URL采集时一个慢站点会拖垮整个任务的进度。我通常在日志里记录每个URL的响应耗时超过3秒的会单独分析原因。3. 解析环节从HTML和JSON中提取数据的四把利器3.1 XPath、BeautifulSoup、PyQuery和正则怎么选数据拿回来之后就是解析。这一步有不少工具可选我简单帮新手做个区分正则表达式适合从纯文本或结构凌乱的HTML中提取特定模式比如邮箱、手机号、ID一类的固定格式但不适合解析层级复杂的HTML结构BeautifulSoupbs4是最容易上手的HTML解析库它以直观的方式遍历HTML节点对新手极其友好但它在处理大型文档时速度偏慢XPath是基于路径表达式的查询语言能精确定位到某个节点配合lxml库解析速度远快于BeautifulSoup也是我日常的主力方案PyQuery的语法和jQuery几乎一模一样如果你之前写过前端用它是零成本上手用CSS选择器的方式选节点。这么多工具我的建议是新手阶段主攻XPath加lxml这是效率和使用体验的平衡点。XPath的学习成本不高核心就是那几条路径表达式//表示任意层级/表示直接子节点取属性text()取文本再加上一些谓词条件。3.2 XPath实战定位节点、属性与文本提取我用一个实际例子来说明XPath的用法。假设你要采集一个商品列表页每个商品的结构是div classitem a href/goods/12345.html title无线蓝牙耳机 span classprice199.00/span span classsales月销500/span /a /div要提取商品名称和价格XPath写法是这样的from lxml import html page html.fromstring(resp.text) items page.xpath(//div[classitem]) for item in items: title item.xpath(.//a/title)[0] price item.xpath(.//span[classprice]/text())[0] print(title, price)这里要注意两个细节。第一在item内部继续查找时要用./前缀即.//表示从当前节点开始向下查找而不是从整篇文档重新找这能避免乱选到其他模块的同名节点。第二xpath返回的是列表如果匹配不到节点取[0]会报IndexError所以取数前先判断列表是否为空会更稳。3.3 JSON数据的解析技巧与异常字符处理异步加载的网站返回的数据基本是JSON格式在响应里能看到一串字符串直接解析就行。处理JSON时我习惯先用json.loads把文本转成Python字典再用嵌套的键访问来取数据。但嵌套层级深了之后连续用data[data][list][0][title]这种写法会让人崩溃。更好的方式是写一层一层的小函数或者直接用pandas的json_normalize展开。JSON解析中还有个常见坑数据里含有不规范的转义字符直接json.loads会抛异常。通常的应对是在加载前先做一次简单的字符清洗把控制字符去掉。import re import json def safe_json_loads(text): # 清除控制字符 text re.sub(r[\x00-\x1f\x7f], , text) return json.loads(text)另外注意部分站点返回的不是标准JSON而是JSONP请求带上回调函数的格式此时需要先剥离掉前后的callback包裹再进入json解析流程。4. 反爬机制识别与破解实用技巧4.1 请求头校验、请求频率与IP封锁的应对思路请求头校验是最低级别的反爬解决办法就是我前面说的把Headers补全尤其是UA和Referer。再高级一些的网站会分析请求频率一个IP在短时间内发起太多次请求直接触发风险控制。这种时候最简单的策略是降低频率加随机延时比如每次请求间隔随机在1秒到3秒之间。其次是用IP代理池把请求分散到不同的出口IP上。代理池的搭建思路也有讲究。免费的代理不稳定连接成功率很低实测定时挂。要建一个能用的代理池通常需要以下几个步骤收集代理源免费代理网站、定时验证代理可用性发一个测试请求、剔除失效IP、按速度排序。用到的库有requests加redis之类的队列。等我后来做分布式爬虫时代理池就变成了一个独立的服务对接多个采集节点这也是后话了。4.2 字体反爬和图片混淆的原理与破解字体反爬是一种很有意思的反爬技术常见于一些招聘平台和房产平台。原理是网页上显示的文字是一套自定义字体渲染出来的你在HTML源码里看到的字符编码和实际显示出来的文字不一样。比如页面上显示“北京”源码里可能是“鍖椾含”。它通过引入自定义字体文件.woff或.ttf做一个字符映射人眼看到的是对的但爬虫直接抓源码时抓到的就是乱码。破解字体反爬的思路是下载页面引用的字体文件解析里面的字符编码到glyph名称的映射再对比字体文件和真实文字之间的对应关系。我通常用fontTools库来解析字体文件核心代码逻辑是遍历字体里的cmap表得到每个字符码对应的字形名称然后利用字体编辑器或者在线工具建立“字形名称到真实汉字”的映射表。要注意的是有些站点会定期更换字体文件映射关系也会跟着变所以这个映射表得动态更新不能写死。图片混淆则是指数字以背景图片或CSS偏移的方式呈现比如页面上价格里的数字其实是小图片拼出来的。这种用OCR或者模板匹配都能解决但成本较高。如果数据量不大或者对自动化要求不高最务实的方式是人工介入从页面上直接识别。爬虫不是万能的有些反爬的成本高到不值得去破这是我做项目的一个底线判断。4.3 滑块验证码与行为轨迹模拟从识别到绕过滑块验证码是目前最常见的反爬手段之一尤其是拖动式滑块。它不仅仅是让你把滑块拖到指定位置而是在背后分析你的拖动轨迹是否像人类。如果你瞬间从起点瞬移到终点哪怕是正确的位移量也会被判定为机器操作。应对滑块验证码有几种思路一是接第三方打码平台把验证码图片发给平台平台返回需要的位移距离或直接返回验证结果。这种方式速度快成功率稳定但成本偏高。二是在本地用OpenCV做缺口识别通过边缘检测算法找出滑块和缺口的位置然后计算位移量再用selenium模拟拖动。三是自己模拟人类拖动轨迹关键是轨迹不能是一条直线要有加速减速的过程还要有轻微的抖动。我排序下来稳的还是第三方平台尤其是企业级的采集项目。本地自己搞的一是识别准确率不稳定二是即使识别对了轨迹不够拟真还是会失败。当然如果只是学习技术用OpenCV做缺口识别是一件很有成就感的事。4.4 JS加密参数与浏览器指纹升级版挑战现在不少做风控的站点会要求在请求里带上一串加密的sign参数。这个sign是怎么生成的一般是根据当前时间和一些固定的参数拼接再加上盐用MD5或SHA系列散列算法算出来的。完整的逆向流程是在开发者工具里定位到参数生成的位置通常在Sources面板里搜索和参数名相关的字符串打断点单步调试观察这个值是用什么函数怎么算出来的。这里需要用到JavaScript的调试能力也是爬虫进阶的分水岭。另一种更隐蔽的技术是浏览器指纹识别它收集你浏览器的Canvas指纹、WebGL信息、时区、语言、字体列表等等形成一个多维度的ID来识别你到底是不是同一个用户。应对方案是保证同一浏览器实例的指纹稳定或者干脆用指纹修改插件。做项目时我对这类站点第一反应是评估采集的必要性和性价比如果数据价值撑不起破解成本就换别的数据源或者换别的方案。4.5 验证码识别的工具链与实战经验关于验证码识别我再补充几句。纯数字和字母的简单验证码用打码平台时基本就是秒过。复杂一点的滑动和点选也能找到对应的服务。真正难搞的是那些行为验证比如需要完成特定的人机交互任务。这种时候我更倾向考虑用真实浏览器环境配合人的操作来绕过而不是纯自动化。我个人的经验是验证码识别这件事能花钱解决的问题不要花时间。时间成本远高于平台服务费。除非你明确是为了学习算法否则别陷进去。新手在学爬虫时往往会对验证码产生执念觉得一定要亲手破解才有成就感。我理解但真的不划算。你的精力应该花在数据解析和架构设计上这些才是核心能力。5. 爬虫框架升级从模块化脚本到Scrapy与分布式5.1 为什么要从Requests升级到Scrapy当你手头有几十个采集任务分布在不同的网站上每个任务都需要定时调度、增量爬取、失败重试和数据清洗这时候用Requests写脚本就会变得非常痛苦。Scrapy出现的原因正是为了解决这些问题。Scrapy的核心优势总结下来是这几个异步并发采集不用你自己管理线程池内置去重队列基于URL指纹能自动过滤已抓取的链接通过Item Pipeline把数据加工和存储流程化支持扩展中间件可以自由注入代理、UA随机、自定义下载延时等逻辑还有Scrapy Shell这个调试神器写XPath时可以实时验证。我用Scrapy写一个新的爬虫项目从创建项目到跑通数据入库通常只需要半小时到一小时。而这个流程如果用Requests每个细节都要自己搭工作量至少翻倍。这不是说Requests不好而是工具要用在合适的地方。5.2 Scrapy关键组件与一个可运行的入门示例Scrapy的目录结构通常包含items.py定义要采集的数据字段、pipelines.py数据处理和存储、spiders目录爬虫主体逻辑、settings.py全局配置。新建一个爬虫的常用命令scrapy startproject myspider cd myspider scrapy genspider example example.com我给一个最简单的爬虫示例目标是解析一个列表页的标题import scrapy class ExampleSpider(scrapy.Spider): name example start_urls [https://example.com] def parse(self, response): self.log(页面标题: %s % response.css(title::text).get()) # 提取列表中所有商品详情页链接回调 parse_detail for href in response.xpath(//div[classitem]/a/href).getall(): yield scrapy.Request(urlresponse.urljoin(href), callbackself.parse_detail) def parse_detail(self, response): title response.xpath(//h1/text()).get() price response.xpath(//span[classprice]/text()).get() yield { title: title, price: price, }注意parse方法里用yield产出字典Scrapy会自动把生成的Item交给Pipeline处理。你不需要自己管理请求队列和并发Scrapy全包了。5.3 分布式爬虫架构Scrapy-Redis与任务调度当单机跑满了并发还是不够快或者单机被目标站点封锁了IP就得上分布式爬虫。思路很简单多台机器共同消费同一个任务队列切片去重和数据加工层共享。Scrapy-Redis是Scrapy实现分布式最常用的方案它把Requests队列、Item队列和去重指纹都存到Redis里多台机器上的Spider实例从同一个Redis队列里取任务保证每台机器不会重复采集同一批URL。流程简化为: - 调度器从Redis取URL - 下载页面 - 解析数据 - 存入MySQL/MongoDB - 发现新URL推回Redis需要注意的点是去重指纹默认是请求的URL做sha1若同一个页面有多处入口链接导致参数不同可能会重复采集此时需要自己重写指纹生成逻辑。另外分布式爬虫的代理池、账号池、反爬策略都需要独立维护架构复杂度会上升一个量级。我的建议是先把单机水平练扎实了数据量确实到了需要分布式的时候再上不要为了分布式而分布式。6. 新手常踩的坑与实用调试技巧6.1 请求频率过高导致IP被封这绝对是我见过最多的新手问题。很多人在本地测试时加个循环几十个请求一口气发出去没一会儿就发现自己的IP被目标网站封了。被封的典型表现是普通浏览器访问还是正常的但爬虫的所有请求都返回403或跳转验证码页。解决方案就是我在请求封装里说的那套控制QPS、随机延时、配合代理池。还有一点很重要上线爬虫前要评估目标站点服务端的承受能力。有些小网站就是一个单机服务器没什么反爬能力你一个高并发的爬虫冲过去可能直接把人家服务打挂这是很不好的行为也容易引来法律风险。合理设置采集频率既是对网站的保护也是你爬虫能长期生存的保障。6.2 数据解析取不到值先回头看返回的数据新手在解析阶段经常会遇到xpath/extract出来是空列表或者None的情况。这时候大多数人会在XPath表达式上反复折腾。我的建议是先别急着改表达式把响应内容打印出来看一眼。用scrapy shell或requests的resp.text打印一段HTML用人眼确认你要的数据是不是真的在这个HTML里长什么样嵌套在哪一层。很多时候取不到值是因为数据是JS动态加载的HTML源码里根本没有你再怎么调XPath都没用。这时候得回去找异步接口。这类问题的排查步骤我写过无数遍核心就一句数据不在响应里再厉害的解析也白搭。6.3 数据落地时编码与重复问题数据入库常见的坑是编码。MySQL的utf8mb4字符集对emoji和一些生僻汉字是刚需如果你用utf8插入带生僻字的数据时会直接报错。所以建库建表的字符集要提前选好。重复数据的问题是另一个高频坑。同一个URL被多个入口链接引用去重指纹又是基于URL做的结果就是同一篇内容被存了两份。解决方案有两个层面一是在爬虫层用Scrapy自带的dupefilter二是在数据库层给业务唯一键加唯一索引。我在做新闻类网站采集时通常用URL的哈希值作为主键这样即使重复入库也会被数据库直接挡掉省去了写一堆去重判断的代码。6.4 页面结构变更导致爬虫失效的应对策略爬虫最让人头疼的问题是维护成本。今天的网站改版一下把class名从item改成goods-item你的XPath就全断了。这是每个爬虫工程师都会遇到的事情无法避免只能尽量降低影响。我的应对策略是把网站的解析逻辑和请求逻辑分离标题、正文、时间这些字段的选择器统一放在一个配置项里当页面结构变更时只改配置不用动爬虫代码。再一个是用相对稳定的特征比如有些站点的class名会带hash后缀每次构建都会变但有稳定id的标签或DOM结构特征不会变优先用这些。最后是定时巡检用钉钉或企业微信的机器人发告警能在爬虫失效的第一时间收到通知不至于让数据断层好几天才发现。6.5 一个从0到1的完整实操流程综合案例我把一整套爬虫实操流程串起来给新手一个完整的印象。假设我们要采集某个资讯站的新闻列表该站点是服务端渲染不需要登录也没有JS渲染。步骤一URL分析。打开列表页按F12看Network找到列表页的URL规律是https://example.com/news/page/1页码递增。步骤二写基础请求。用我上面给的fetch_url函数带上UA和Referer请求第一页打印状态码和响应内容确认能拿到数据。步骤三解析数据。用lxml写XPath提取每篇文章的标题、链接、发布时间和摘要先提取前几篇验证结果。步骤四翻页采集。改成for循环遍历前5页每页之间加随机延时。步骤五数据去重入库。引入一个简单的SQLite或MySQL表用URL的哈希作为主键。步骤六升级为Scrapy。当页面数量增加到几千甚至几万时把上面的逻辑迁移到Scrapy项目里加ImagePipeline和MongoDB存储加上AutoThrottle自动限速。步骤七部署定时任务。用Cron或APScheduler让爬虫每天凌晨自动运行一次增量抓取当日新发布的文章。这个流程走完你对爬虫的基础认知就建立完善了后面遇到再复杂的站点也都是在这个框架里添加反爬破解的应对措施而已。7. 规模化采集的进阶思路IP代理池、全站爬取策略与工程化思考7.1 IP代理池的搭建与自动验证代理池的搭建对做规模化采集几乎是必备的。我自己的一个简化版方案是抓取免费代理网站的代理列表然后写一个验证脚本用代理去访问一个固定的探测URL能成功返回就说明可用同时记录响应速度。把这些可用的代理存到Redis的set里供采集节点消费。import requests def validate_proxy(proxy): test_url https://httpbin.org/ip proxies {http: proxy, https: proxy} try: resp requests.get(test_url, proxiesproxies, timeout5) if resp.status_code 200: return True except Exception: pass return False这里要注意的是代理验证的探测URL最好用http和https分开测并且要测多次因为不少代理是间歇性可用的。每次使用时代理的稳定性都需要做一次轮询检测失效了立刻从池里剔除。做代理池的过程比较枯燥但它是保证大规模采集不挂掉的关键支撑之一。7.2 全站爬取策略URL去重、增量更新与广度优先全站爬取的场景是一个网站有数万甚至数百万个页面你不能简单地从第1页到第N页顺序遍历因为很多页面并不在列表页里只能通过HTML中的链接一路发现下去。这种场景下我通常采用广度优先的BFS策略从种子页开始解析页面里的所有同站链接放入待抓取队列从中取出一个继续抓不断追加新发现的链接直到队列为空。去重逻辑在这种场景下格外重要Scrapy默认的RFPDupeFilter基于sha1指纹能过滤重复的URL。但如果链接带排序参数、追踪参数同样的页面可能生成多个不同的URL造成重复抓取。解决方法是自定义指纹生成函数在计算指纹前先把无意义的参数删掉或者对URL做归一化处理。增量更新解决的是“我昨天抓过了今天只需要抓新增和修改的页面”的问题。最朴素的做法是保存每个页面的最后抓取时间在调度队列前判断是否超过更新阈值。如果网站有sitemap.xml直接用sitemap提取需要更新的URL清单是最省事的方式。7.3 爬虫工程化的经验总结最后聊点工程化的体会。爬虫项目到了一定规模它的难点已经不只是“能不能抓到数据”而是“能不能稳定地、规模化地抓到数据”。这就要求你在代码之外关注监控、日志、告警和任务调度这些基础设施。我做爬虫项目有一个习惯每个爬虫任务尽量输出结构化的日志包含任务ID、采集数量、失败原因、耗时等关键信息。日志统一打到同一个平台比如ELK或者简单地存到数据库方便定位问题。同时给每个任务设置失败告警一旦采集成功率低于阈值或者连续多次失败立刻通知值班的人。这样跑一段时间你就能积累出每个目标站的“健康档案”哪个站稳定哪个站经常出幺蛾子心里一清二楚。另外别忽视数据质量校验。解析出来的字段是否为空、类型是否正确、编码是否正常这些都要在入库前做一次校验和清洗。脏数据进了库再想治理成本比爬取本身高得多。我通常用pydantic或者简单的自定义校验函数做这道防线校验不过的数据进不了数据库并单独记录到异常表里方便回溯。8. 从爬到取的价值链条与效率优化8.1 数据清洗、去重与入库爬虫只是第一步很多人以为爬虫跑完、数据入库就完事了。实际上爬回来的原始数据通常是杂乱不堪的HTML标签混在正文里、时间格式不统一、价格字段有逗号或货币符号、同一篇文章被不同站转载完全重复。入库前做清洗处理是确保后续数据分析可用的大前提。我的典型流程是清洗HTML标签 - 提取正文纯文本 - 统一时间格式为标准时间戳 - 统一数字格式 - 对核心字段做非空校验 - 生成内容指纹用于去重 - 入库。这个流程走完后数据才是真正“干净”的数据。8.2 采集效率评估单机到分布式的量化指标最后聊一个很多新手忽略的点怎么评估采集效率。单机Requests跑单线程加延时每秒大约是1到2个请求一天理论能跑到10万页的量级。切换到Scrapy的并发模式单机并发调到8到16每秒能到10到30个请求一天能到100万以上。如果你的目标站点反爬严格大量请求会被验证码或封IP拦截实际有效采集量会大打折扣。所以做爬虫项目第一步永远是估算数据量和目标站点的反爬强度再倒推需要什么样的架构和配置。我见过不少团队在数据量只有几十万的时候就开始搭十几台机器的分布式集群最后发现维护成本远高于收益。适合的才是最好的。8.3 一次小规模采集项目的完整复盘我用一个实际项目来收尾。之前接过一个采集某资讯网站内容的需求数据量大约5万篇文章目标站开启了一定程度的防爬IP请求太频繁会弹验证码。我的做法是先用Requests写好和解析逻辑本地跑通第一版确认能拿到数据然后因为是多页列表加详情页的结构直接切换成Scrapy把解析逻辑迁移过去开8个并发配置AutoThrottle自动根据响应时间调整延时代理方面配了20多个自建代理跑完整个采集任务大约用了6个小时。入库后做了标题去重和正文清洗最终入库4万7千条有效数据。这个项目从接手到交付总共花了不到两天。如果当初我用Requests单线程去跑5万页大概要跑三天三夜中途还会因为IP频繁被封而不断重试整个项目体验会差很多。这就是工具选型带来的实际差异。我个人的体会是爬虫这个技术领域真正拉开差距的不是你掌握了多少花哨的反爬技巧而是你能不能根据目标站的实际情况快速做出合适的技术方案。能简单的绝不复杂化能用并发解决的不用分布式能用现成打码平台解决的不耗费几天去逆向JS。所有技术都是为了稳定、高效、可持续地拿到数据服务。希望这篇经验分享能帮你少走一些我当年走过的弯路让你在面对爬虫项目时心里先有一个完整的作战地图。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价