楼层: 首页/ 软件技术/ Python 爬虫技术/ 进阶与最佳实践
10

进阶与最佳实践

Best Practices

爬虫写到一定程度,拼的不是"能跑",是稳、快、不被封。这章讲工程化经验。

分布式爬虫

# Scrapy-Redis:多机共享请求队列 pip install scrapy-redis # settings.py 把 scheduler 换成 Redis 版 SCHEDULER = "scrapy_redis.scheduler.Scheduler" DUPEFILTER_CLASS = "scrapy_redis.dupefilter.RFPDupeFilter" REDIS_URL = "redis://localhost:6379/0" # 多台机器跑同一个 spider,共享 Redis 队列,不重复爬

去重与断点续爬

论怎么不重复爬

集合去重:已爬 URL 存 set,内存够就行。Redis 去重:大规模用 Redis set,跨进程共享。布隆过滤器:URL 上亿时用,省内存。断点续爬:爬一半崩了,把已爬 URL 存文件/Redis,下次从断点继续。

增量爬取

论怎么只爬新内容

时间戳法:记录上次爬到的最新文章时间,下次只爬比这个新的。哈希法:把文章内容算个 MD5,存下来,下次先比对再决定爬不爬。URL 去重:已爬 URL 存 Redis/文件,遇到跳过。增量爬取是监控类爬虫的核心——每天跑一次,只入库新内容。

布隆过滤器:亿级 URL 去重

# URL 上亿时,set 装不下,用布隆过滤器 pip install bloom-filter from bloom_filter import BloomFilter bf = BloomFilter(capacity=10_000_000, error_rate=0.001) if url not in bf: # 没爬过,去爬 bf.add(url) # 缺点:有误判(说爬过了其实没爬过),但内存极小

爬虫项目结构规范

my_spider/ ├── spiders/ │ ├── douban.py # 各站点爬虫 │ └── news.py ├── parsers/ # 解析逻辑 ├── storage/ # 存数据库 ├── utils/ # UA池/代理池/重试 ├── config.py # 配置 └── main.py # 入口

API 优先原则

论能调 API 就别爬 HTML

现代网站的后端 API 返回结构化 JSON,比爬 HTML 快 10 倍、稳 100 倍。打开 DevTools Network,找 XHR/Fetch 请求,看哪个接口返回了你要的数据。直接调那个接口,省得解析 HTML 还能跳过前端反爬。找接口 > 爬 HTML > Selenium,优先级从高到低。

遵守 robots.txt

# robots.txt 就是网站的"请勿打扰牌" # 例:https://example.com/robots.txt User-agent: * Disallow: /admin/ Disallow: /private/ Allow: /public/ # 写了 Disallow 的路径就别爬,君子协定

分布式爬虫:Scrapy-Redis

论什么时候要分布式

单机爬几万页面够了。但要爬几十万、上百万,单台机器带宽和CPU都顶不住。Scrapy-Redis 把请求队列放 Redis,多台机器跑同一个 spider,共享队列、共享去重,一台挂了另一台接着爬。这是工业级爬虫的标配。

编码问题怎么排查

# 中文乱码?三步走 resp = requests.get(url) print(resp.encoding) # 看当前编码对不对 print(resp.apparent_encoding) # 库猜的真实编码 resp.encoding = "gbk" # 手动改 # 现代网站基本 UTF-8,老网站可能 GBK

数据质量验证

论爬完不等于爬对

行数对不对:预期 250 条,实际多少条?空值多不多:评分/标题有没有空?抽样看几条:随机抽 5 条手动对一下原网页。去重:有没有重复 URL。数据爬完先验证,别急着入库分析。

日志与异常处理

import logging logging.basicConfig( level=logging.INFO, format="%(asctime)s [%(levelname)s] %(message)s", ) log = logging.getLogger(__name__) try: resp = requests.get(url, timeout=10) resp.raise_for_status() except requests.exceptions.RequestException as e: log.error(f"请求失败 {url}: {e}")

限流策略:别打挂对方

策略怎么做
固定间隔time.sleep(1),简单但容易被识别
随机间隔time.sleep(random.uniform(1, 3)),更像人
令牌桶每秒最多 N 个请求,平滑
Semaphore异步并发限流,同时最多 N 个

数据清洗

# 爬下来的数据要清洗:去 HTML 标签、去空白、转类型 import re def clean_html(raw): """去掉所有 HTML 标签""" return re.sub(r"<[^>]+>", "", raw).strip() def parse_price(s): """'¥12.50' -> 12.5""" m = re.search(r"[\d.]+", s) return float(m.group()) if m else None def clean_whitespace(s): """' 你好 世界 ' -> '你好 世界'""" return re.sub(r"\s+", " ", s).strip() def parse_date(s): """'2026-09-16' -> datetime""" from datetime import datetime return datetime.strptime(s, "%Y-%m-%d")

重试逻辑:失败自动重试

import time def fetch_with_retry(url, retries=3): for i in range(retries): try: resp = requests.get(url, timeout=10) resp.raise_for_status() return resp except Exception as e: print(f"第{i+1}次失败:{e}") time.sleep(2 ** i) # 指数退避:2,4,8秒 return None

常见坑

坑怎么避
编码乱码中文网站有时是 GBK,resp.encoding = "gbk" 或看 resp.apparent_encoding。
HTML 不规范用 lxml 解析器,比 html.parser 容错好。
动态内容抓不到HTML 里没数据?开 DevTools Network 找 JSON 接口。
被封不知道检查 status_code,403/429 要换代理/降速。
Cookie 过期定期重新登录,或用 Session 保持。
HTML 结构变了网站改版,选择器失效。定期检查,选择器写稳一点。
没存 Cookie 就翻页第一页能看第二页 403。用 Session 保持。
没设超时对方不响应,程序永远挂着。必须 timeout。

数据量与带宽预估

论爬之前先算笔账

一页 100KB,爬 1 万页 = 1GB 流量。100 个并发、每个 1 秒返回 = 100 秒爬完。但对方服务器扛不扛得住?你家带宽够不够?先小批量试,再放量,别一上来就并发 1000。

新手最常犯的 5 个错

错后果
不设 timeout程序永远挂着
不设请求间隔秒被封 IP
不带 User-Agent403 拒绝
不看状态码404 还在那解析
乱爬敏感数据法律风险
不做去重同一条数据爬 N 遍
不存断点爬一半崩了从头再来
不查状态码403 了还以为是自己解析错
不设 Referer防盗链网站直接 403
不分页只爬第一页漏了 90% 数据
编码不对存 CSVExcel 打开乱码
不处理重定向301 后丢 Cookie
不设重试一次失败就跳过
不验证数据爬了一堆空值不知道
不写日志跑崩了不知道哪错了
不分模块一个文件 1000 行没法维护
不管编码中文全是乱码
不更新代码网站改版了还按老选择器爬
不试小批量一上来就 1000 并发
不存原始 HTML解析错了没法回头查
不做监控爬虫挂了几天不知道

推荐学习资源

论学爬虫去哪学

官方文档:requests / BeautifulSoup / Scrapy / Playwright 官方文档都很全,是第一手资料。DevTools:所有反爬手段的答案都在浏览器 F12 里。动手:找个公开 API 站(如 httpbin.org)先练手,别一上来就爬真实站。

爬虫工具生态一览

工具什么时候用
requests入门、少量请求、调 API
aiohttp异步并发,几百上千个请求
BeautifulSoup解析 HTML,入门友好
lxml解析 HTML,快,XPath
Selenium动态页面、必须登录、模拟浏览器
Playwright现代浏览器自动化,比 Selenium 稳
Scrapy工程化爬虫,整站爬
Scrapy-Redis分布式爬虫,多机协作
高频面试题 15 道(挑重点)

1.(概念题)requests 和 aiohttp 的本质区别?

答案

requests 同步阻塞,发一个等一个;aiohttp 异步,一个线程挂起几百个请求同时飞。IO 密集型 aiohttp 快 10 倍以上。

2.(概念题)什么是 robots.txt?必须遵守吗?

答案

网站根目录的规则文件,写了哪些路径不让爬。不是法律,是君子协定。尊重它,别爬 Disallow 的路径。

3.(概念题)Scrapy 的 Pipeline 什么时候用?

答案

数据清洗、去重、存数据库时。Spider 只管提取,Pipeline 管存储,职责分离。

4.(概念题)为什么用 Semaphore 限并发?

答案

无限制并发会把对方服务器打挂、自己被封 IP。Semaphore 限制同时最多 N 个请求,既快又礼貌。

5.(概念题)被封 IP 了怎么办?

答案

换代理池、降速、换 UA、加 Cookie 池。先搞清楚是临时封还是永久封,别一上来就换代理。

6.(概念题)动态渲染页面怎么抓数据?

答案

① 找后端 JSON 接口(最推荐);② Selenium/Playwright 模拟浏览器渲染;③ 逆向 JS 加密参数。

7.(概念题)怎么处理验证码?

答案

简单验证码用 ddddocr/tesseract;复杂的用打码平台(人工识别)。别自己写模型训,不划算。

8.(概念题)什么是增量爬取?

答案

只爬新内容,不重复爬。用时间戳/哈希判断"这篇上次爬过没"。常用于监控类爬虫。

9.(代码题)怎么下载一张图片?

答案

r = requests.get(img_url); open("a.jpg","wb").write(r.content)。用 content 字节,不是 text。

10.(概念题)HTML 解析用 BS4 还是 XPath?

答案

入门小脚本 BS4 直观;大规模用 lxml+XPath 快 3-10 倍。功能上能互相替代。

11.(概念题)爬虫怎么存 Cookie?

答案

requests.Session() 自动内存里存;要持久化用 LWPCookieJar 存文件。aiohttp 用 CookieJar。

12.(概念题)什么是中间件?Scrapy 中间件干嘛用?

答案

Downloader Middleware 在请求发出前/响应回来后插一脚:加 UA、换代理、重试。Spider Middleware 在解析前后插一脚。

13.(概念题)为什么别爬登录墙后数据?

答案

未授权访问,法律风险。要么用自己账号按规矩用,要么找公开数据。

14.(代码题)怎么设置超时?

答案

requests.get(url, timeout=10)。不设超时程序会永远挂着。

15.(思考题)API 优先原则是什么?

答案

"能调 API 就别爬 HTML"——API 返回结构化 JSON,稳定、快、不依赖页面结构。先找后端接口,找不到再爬 HTML。