简介:这套压缩包面向Python爬虫初学者与进阶者,聚焦Amazon、Confluence等网站的页面采集任务。项目spider-master内置多组爬虫脚本,覆盖静态抓取、动态渲染、登录会话、代理切换等常见技术点,读者可借此理解requests、BeautifulSoup、Scrapy的协作方式。压缩包共41个文件,以31个py脚本为核心,辅以3个md说明、3个cfg工程配置与2个json代理数据,整体仅47KB,轻量且便于逐行研读。资源已有466人学习,脚本还包含贴吧、糗事百科、人名采集等示例,并配置了中间件、请求头伪装等模块,便于拓展为个人爬虫工具箱。通过该项目,读者可获得一套结构清晰、可运行的爬虫模板,掌握数据解析、异常处理与合规抓取的综合实战思路。
1. 这个 python 爬虫资源包,解决的问题比想象中更具体
做数据采集的同行应该都有这种体会:单写一个爬虫不难,难的是同时面对多种目标站点、多种反爬策略时,还能保持代码结构清晰、可维护、可复用。这个名为“python 爬虫(amazon, confluence ...)-spider.zip”的资源包,就是从电商详情页和企业知识库两类典型场景切入的采集方案集合。它不是那种教学性质的单文件 demo,而是一套有目录分层、有公共模块、有配置分区的工程结构。适合正在进行数据采集、竞品监控、内部文档备份迁移的开发者,尤其是对电商商品跟踪和企业内部知识库同步有需求的团队。换句话说,如果你只需要一个能跑通的脚本,这个包可能“过重”;但如果你需要的是能长期维护、遇到反爬能兜底、换站点能快速适配的采集框架,它就是很好的起点。
2. 拆开 spider.zip:目录结构与模块运行机制
拿到压缩包,先不要急着跑任何脚本。第一步是把压缩包解压后,对着目录结构快速浏览一遍。好的爬虫工程和差的爬虫脚本,区别就在目录分层的清晰度上。这个包的整体布局很像一个被实际使用过一段时间的内部项目,不是临时拼凑的。
2.1 顶层目录规划:哪个目录负责哪一类采集任务
解压后首先看到的是按照目标站点类型划分的目录,比如amazon_spider/、confluence_spider/,以及一个common/公共模块目录。这里的命名思路值得直接复制到你自己项目里:按数据来源站点划分采集目录,而不是按数据类型划分。因为电商商品和知识库页面相比,无论从数据结构、反爬强度、还是更新频率上看,差异都远大于共性,把它们塞进同一个通用爬虫里只会让代码里的逻辑分支越来越多,最终变成无人敢改的“屎山”。
common/目录下放的是所有站点采集任务共用的能力,包括请求发送与重试、Headers 伪装、代理切换、User-Agent 轮换、日志记录、数据落库中间件。这种结构的好处是明显的——当你发现某个代理 IP 被目标站点封了、需要换一批新代理时,只需要修改common/proxy_pool.py或者对应的代理配置文件,全站点的爬虫都会生效,不需要逐个爬虫文件去改。我一般会把这种方式叫作“改动收敛”,对于长期迭代的采集项目来说,这是刚需。
2.2 配置文件与启动入口:快速定位每个爬虫的开关
每个站点目录下都有自己独立的config.py和run.py。config.py存放目标 URL 模板、请求头、采集深度、页面解析规则等参数;run.py是该站点爬虫的启动入口。这种“一站点一配置、一站点一入口”的设计,能让不同站点的采集参数互不干扰,也方便在调试时只启动某个站点的采集。比如你只想测试电商商品页新增的某个字段,直接运行对应目录下的run.py即可,所有配置从本站点的config.py读取。常见的错误做法是把所有配置都堆在一个settings.py里,然后靠if site == 'amazon':来区分逻辑,一旦站点超过三个,配置文件就会变得冗长且容易改错。
2.3 最简单的一次启动流程:从安装依赖到生成结果
假设你要跑通电商采集模块,操作流程大致如下:
cd amazon_spider pip install -r requirements.txt python run.py --pages 5先把命令拆开解释。cd amazon_spider进入电商采集目录,pip install安装该模块的依赖,python run.py --pages 5启动采集并抓取前 5 页商品列表。如果你用的是虚拟环境(我强烈建议用),上述命令需要先激活你的虚拟环境再做。
requirements.txt依赖列表里一般包含requests、parsel或者BeautifulSoup4、lxml这类解析库。--pages参数控制采集页数上限,默认值通常在config.py里设定。整个流程就是:启动入口 → 读取配置 → 组装请求 → 发送请求 → 解析响应 → 结构化输出。这套流程本身并不复杂,复杂的是每个环节里的参数设置和异常处理,这也是接下来要具体展开的部分。
3. 电商商品采集:商品列表页抓取与两个拦路虎
电商采集是这份资源包里最常用的模块。以某头部电商平台为例,它的商品列表页包含标题、价格、评分、评论数、封面图链接等关键字段,是竞品分析和选品调研的主力数据源。
3.1 列表页解析规则:定位商品节点与提取核心字段
解析列表页是这个模块的核心逻辑。电商列表页的结构通常是:一个商品列表容器节点下,分布着多个商品卡片节点。解析的思路是先把所有商品卡片节点定位出来,再逐一从卡片中提取字段。用parsel库实现时,典型的 xpath 规则如下:
import parsel def parse_listing(html): selector = parsel.Selector(text=html) products = selector.xpath('//div[contains(@class, "s-item")]') results = [] for product in products: item = { "title": product.xpath('.//span[contains(@class, "title")]/text()').get(), "price": product.xpath('.//span[contains(@class, "price")]/text()').get(), "rating": product.xpath('.//span[contains(@class, "rating")]/@aria-label').get(), "review_count": product.xpath('.//span[contains(@class, "reviews")]/text()').get(), } results.append(item) return results这里有几个参数细节需要说明清楚。//div[contains(@class, "s-item")]是定位商品卡片的容器,contains方法处理的是“class 属性里包含指定值”的情况,比直接用@class="s-item"更健壮,因为实际页面里 class 经常会同时包含多个值。./表示相对路径,表示从当前商品卡片节点内部继续查找,避免误抓到其他区域的相同 class 节点。.get()取第一个匹配值,.getall()返回所有匹配值。如果字段取出来是None,说明当前页面的 DOM 结构和预设规则不一致,需要重新打开目标页面核对。
3.2 翻页机制与 URL 参数:page 参数的边界条件
电商列表页的翻页逻辑并不复杂,但有一个参数边界问题值得单独拿出来说。大部分电商平台通过 URL 中的page参数控制页码,比如https://xxx.com/s?k=keyword&page=2。爬虫只需要循环修改page参数即可。但如果你要采集“按照相关性排序”的商品列表,翻到后面会出现大量与前几页相似甚至重复的商品。更麻烦的是,价格和评分这类动态数据,每次请求返回的结果可能不同,所以翻页的边界条件需要结合“页面内是否还有新的商品节点”来判断,而不仅是“是否达到预设页数上限”。
for page in range(1, max_pages + 1): params = {"k": keyword, "page": page} html = fetch(params) items = parse_listing(html) if not items: break save_to_db(items)这段代码里有一个关键动作:if not items: break。当当前页解析出来的商品列表为空时,说明要么翻到了尽头,要么触发了验证码或者封禁,无论哪种情况继续翻页都没有意义了。这个判断条件要放在“写入数据库之后、下一次请求之前”,既保证已有数据已保存,又避免对无效页面浪费时间。
3.3 第一个拦路虎:频繁请求导致的下架策略
电商平台对爬虫流量的识别非常敏锐。现象是:前几十页正常,翻到某个节点后突然开始返回异常页面(可能是让你输入验证码、也可能是直接返回错误页)。原因很简单——单个 IP 的请求频率超过阈值后被识别了。解决思路有两个方向:第一是降低请求频率,在每次请求之间加一个可控的随机延时;第二是切换出口 IP(即使用代理),让每次请求的出口 IP 各不相同。这个包里已经内置了请求延时模块和代理模块的对接入口,你只需要在config.py里把延时参数调大,并配置好代理列表即可。需要强调的是,代理端口连接这一层通常需要你自己准备代理服务,资源包只负责调用,不内置任何代理服务能力。
import time import random time.sleep(random.uniform(2, 5))延时范围的设定有一些经验可以参考:如果只是单机单 IP 采集,每两次请求之间延时 2 到 5 秒通常能降低被识别概率;如果目标平台风控较敏感,可以把延时范围提高到 5 到 10 秒。延时不是越长越好,太长的延时会拖慢整体采集效率,需要在“效率”和“稳定”之间找一个平衡点。
3.4 第二个拦路虎:动态渲染的字段缺失
另一个常见问题是:页面上的某些字段(比如折扣价格、库存状态)是通过异步请求加载的,直接请求列表页的 HTML 里拿不到完整数据。排查办法是:用浏览器打开目标页面,右键检查网络请求,看看页面完整数据是否来自额外的接口调用。如果是,那么爬虫方案就不是“改解析规则”那么简单了,而是需要进一步调用对应的数据接口,或者使用浏览器渲染工具来完整执行页面脚本。这个资源包里对第一种情况(直接请求额外接口)有一个封装好的示例,但浏览器渲染这类方案(比如使用无头浏览器)比较重,依赖安装也相对繁琐,一般不建议普通采集场景直接用,先用接口方案,接口不行再考虑渲染方案。
4. 企业内部知识库采集:认证鉴权与增量同步
如果说电商采集考验的是“反爬对抗”,那么知识库采集考验的就是“认证鉴权和数据同步”。这类系统通常部署在内网或半公共网络环境,页面结构相对规整,反爬压力不大,但登录认证和增量同步做得不好,同样会翻车。
4.1 REST API 优先:避开页面解析的直接方案
针对知识库类系统,优先考虑的是 REST API 而不是页面解析。原因有三个:API 返回的是结构化 JSON 数据,不需要维护脆弱的 xpath 规则;API 有明确的速率限制和分页规范,不容易触发风控;API 响应字段稳定,不像前端页面那样频繁调整 DOM。这个资源包里对知识库系统的采集,默认走的就是 API 路线,整个采集模块更像一个 API 客户端,而不是传统意义的爬虫。
import requests session = requests.Session() session.auth = (username, api_token) response = session.get( "https://wiki.example.com/rest/api/content", params={"limit": 25, "expand": "body.storage"} )注意这里的requests.Session()用法。Session 对象会自动保存 cookie,并在后续请求中复用底层连接,比每请求一次就新建一个 session 效率高得多。session.auth传入的是用户名和 API Token,这是知识库类系统常用的认证方式,比直接传密码更安全。params里的limit控制每页返回条数,expand指定需要展开的响应字段,body.storage表示返回页面的存储格式正文。
4.2 分页遍历与游标:不要用页码假设
知识库 API 的分页通常用游标或者 start 参数实现,与电商列表页的页码逻辑不同。常见错误是写死起始偏移量为 0,然后每次加一个固定步长去翻页。这种写法在数据量少、并发低的场景下能用,但一旦数据量超过几百条,就可能出现漏数据或重复数据。原因在于数据可能在遍历过程中发生了新增或删除,导致“按固定偏移量翻页”的逻辑错位。更稳的做法是“按上一次读取到的最后一条记录的游标继续读取”,或者每次读取时记录当前批次的最大时间戳,下一轮查询只拿该时间戳之后的数据。
4.3 增量同步策略:时间戳和变更记录的配合
知识库的更新频率不高,但单次更新量可能不小(比如批量导入了一批新文档)。对这类数据,全量同步每次都要读取所有内容,浪费带宽和效率;只按修改时间过滤,又会漏掉“被删除的页面”。所以增量同步要分两层做:第一层用时间戳筛选新增和修改的页面;第二层单独跑一个对比任务,把本地已有的页面 ID 列表与远程返回的页面 ID 列表做差集,差集部分作为“需删除”对象处理。这个资源包里已经实现了第一层的逻辑,第二层需要你结合实际业务确认本地存储结构后补充。
last_sync_time = get_last_sync_time() query_params = { "limit": 50, "orderby": "modified", "where": f"modified > '{last_sync_time}'" }这里的orderby用修改时间排序,where子句过滤出比上次同步时间更晚的页面。这样做的好处是,如果中途同步失败,下一次重新同步只需要把last_sync_time设置为上一次成功同步的时间点即可,不会重复拉取历史数据,也不会漏掉关键更新。这个参数的设计算是这份资源包里比较扎实的部分,直接搬到你自己的同步任务里就能用。
5. 高并发下的避坑手册:限流、登录态与代理配置问题
写爬虫的人都有过那种“明明本地跑得好好的,一部署上线就出问题”的经历。这一章不聊架构,只聊我在拆解这个资源包时,发现最容易踩的三个坑,每条都是真实出现的现象、原因和解决路径。
5.1 登录态失效导致的知识库 API 接口 401
现象:刚才还能正常调用的知识库 API,半个小时后突然开始返回 401 错误,代码逻辑没改、参数没变、本地测试也是好的。原因:API Token 没变,但服务端对会话有有效期限制,或者 Token 权限被降级了。解决:第一反应是检查本地存储的 Token 是不是被无意中覆盖了;如果没覆盖,就去后台重新生成一组 Token 并更新到配置里。从这之后,我每次部署采集任务时,都会先确认 Token 的失效日期,然后设置一个定时提醒,在 Token 到期前 5 天重新换新,避免大半夜数据同步中断。
5.2 代理连接耗尽:并发数大于代理连接数
现象:并发提高到 20 后,采集速度不仅没上去,反而频繁出现代理连接超时和 Proxy Connection 错误。原因:资源包里的代理池单 IP 并发连接数有限,超过限制后连接直接排队或者被拒。解决:把代理池配置中的max_connections_per_ip参数调小(比如每个 IP 只允许 2 个并发连接),同时增加代理 IP 的数量,而不是只调高并发数。血的教训是:并发数不是越高越好,它必须和代理池容量匹配。从那次之后,我每调整一个并发参数,都会同步核对代理池的容量数据,不会再只盯着爬虫端的并发数做文章。
5.3 解析规则被页面改版击穿
现象:爬虫脚本跑了三个月一直正常,某一天突然解析结果全是None或者空列表,但用浏览器打开页面看,数据明明还在。原因:页面模版升版了,某个字段的 class 类名从price改成了s-price,或者某个商品容器的 xpath 层级变了。解决:把解析失败时的原始 HTML 保存到本地(这个资源包在debug/目录下预留了位置),再针对保存下来的 HTML 文件单独调试 xpath,而不是对着线上页面反复刷新。这个操作帮我把定位问题的时间缩短了好几倍。以后每次做解析,我都会顺手把原始响应打印到日志里,一旦解析异常,可以直接看日志里的原始结构,不用重新抓包。
5.4 数据库写入与网页解析的并发竞争
现象:多线程写数据库时,报 “Too many connections” 或者数据覆盖错乱,看起来像是数据问题,实际是连接池配置问题。原因:每个线程各自创建一个数据库连接,没有复用,数据库连接数被打满。解决:检查资源包里的数据库连接模块,改成使用全局统一的连接池(比如DBUtils.PooledDB或者 SQLAlchemy 的连接池),设置合理的池大小。配置方面,池大小一般设置为并发数的 1.5 倍到 2 倍比较合适,太小会排队,太大数据库扛不住。别问我怎么知道的,跑爆过一次线上小库后就长记性了。
5.5 采集数据的日期字段错乱问题
现象:采集到的评论日期比实际时间慢了 8 小时,或者时间差看起来毫无规律。原因:目标站点返回的时间是 UTC 时区,存到本地后没有做时区转换。解决:在解析模块里增加pytz或zoneinfo转换,把 UTC 时间统一转成你所在时区的时间再入库。这一步看起来不起眼,被业务方追着问数据怎么不准的时候,谁改谁知道。
6. 延续任务的生命:断点续爬、重试闭环与增量更新
爬虫写完只是第一步,能长期稳定运行才是真正需要动手打磨的地方。这里分享三个让我从“频繁救火”到“基本省心”的关键改进,全部都可以基于这份资源包的现有结构做扩展。
6.1 断点续爬:让异常中断不再从头再来
爬虫任务在夜里中断是常态,学校断电、服务器重启、代理池耗尽,什么原因都可能发生。如果不做断点续爬,白天起来发现任务跑了一半挂掉了,只能从头开始跑,浪费时间也浪费流量。断点续爬的核心是记录“当前采集进度”,而不是“从 URL 开始”。
import json def load_progress(): try: with open("progress.json", "r") as f: return json.load(f) except FileNotFoundError: return {"last_page": 0, "last_item_id": ""} def save_progress(payload): with open("progress.json", "w") as f: json.dump(payload, f)这个实现里没有魔法,load_progress()负责从progress.json文件里读取上次记录的采集位置,save_progress()在每一批数据成功写入后调用一次,保存当前页码和最后一条数据的标识。有了这两个函数,任务启动时先加载进度,采集完一批就更新进度。即使中途挂了,重启后也是从断点继续,而不是回到第 0 页。当时用上断点续爬之后,遇到夜里断线就不再是灾难,起来看一眼进度、重新跑一下命令就继续了。
6.2 失败重试的闭环:退避等待比“立刻重试”更有用
网络请求的失败是常态,但立刻重试往往效果不好——如果目标服务器正处在风控触发状态,或者临时过载,连着重试反而加重了问题。我用的策略是“指数退避”:第一次失败等 2 秒再重试,第二次失败等 4 秒,第三次失败等 8 秒,直到达到最大重试次数。
import time def fetch_with_retry(url, max_retries=3): for attempt in range(max_retries): try: resp = requests.get(url, timeout=10) if resp.status_code == 200: return resp except requests.RequestException: pass time.sleep(2 ** attempt) return None这段代码的timeout=10一个容易被忽略的参数。很多爬虫脚本不设置 timeout,结果就是某个连接卡住时,整个线程被拖死,最终任务卡在队列里一动不动。2 ** attempt就是指数退避的延迟时间,第一次重试等 2 秒,第二次等 4 秒,第三次等 8 秒。另外,如果响应状态码是 404,通常不需要重试,因为这是路径写错了;只有 500、502、503 这类服务器错误才值得重试。把“重试”和“不重试”的范围区分清楚,能让日志清晰不少。
6.3 增量更新的最终形态:每天只跑变动的那部分
把一个采集任务从“全量跑”改成“增量跑”,才算真正解放了维护精力。增量更新的最终形态是:启动时对比“本地已有的数据指纹”和“远程数据源的新增数据”,只拉取差异部分。这个资源包里的知识库采集模块和电商商品跟踪模块,都已经预留了增量入口,核心思想是维护一张“已采集 ID 表”或“最后修改时间表”,每次任务启动时读取这张表,只采集表里不存在的记录。时间长了你会发现,日常的数据量可能只有全量数据的一小部分,跑完全量要两个小时,增量只要五六分钟。这套逻辑在持续了大半年的实际采集任务里,一直是稳定运行的。
从那以后,我每次接到新的采集需求,都会强制自己先想清楚三件事:能不能断点续爬、该不该增量更新、失败时日志里留的信息够不够排查。想清楚了再动手写代码,后期省下来的调试时间远大于前期多花的那半小时。希望这份拆解笔记能帮你在实际采集任务里少踩几个坑。
本文还有配套的精品资源,点击获取