本节摘要:爬虫是"HTTP 客户端 + 解析器 + 调度策略"的组合,自动化脚本则把系统操作交给 Python。本节讲请求与会话、解析的两代工具、把第 7 章重试装饰器与第 8 章并发选型组装成生产级采集器,并郑重讲清合规红线。最后看自动化方向(定时任务、批量文件处理、办公自动化)的常用兵器谱。
一个爬虫再复杂,内核也只有三步:发请求、抽数据、存结果。用 requests + 解析库写一个最小版本:
import requests from bs4 import BeautifulSoup def scrape_quotes(page): resp = requests.get( "https://quotes.toscrape.com/page/{n}".format(n=page), timeout=5, ) resp.raise_for_status() # 4xx/5xx 直接抛(6.2 节 EAFP) soup = BeautifulSoup(resp.text, "html.parser") return [ {"text": q.get_text(strip=True), "author": q.select_one(".author").get_text()} for q in soup.select(".quote") ] scrape_quotes(1)[:2] # [{'text': 'The world as we have created it ...', 'author': 'Albert Einstein'}, ...]
工程化从三个方向长出来:会话复用(requests.Session 保持连接池与 Cookie,量大时快很多)、请求头诚实(User-Agent 标明身份,别伪装浏览器)、结构化抽取(CSS 选择器 select 优先于正则——HTML 是树,BeautifulSoup/lxml 按树查,正则只处理真文本)。现代网页大量数据在 <script> 里的 JSON 中,resp.json() 一步拿到,比解析 DOM 更稳。
裸循环不叫爬虫,生产采集器至少要有重试、限速、并发三件。第 7.2 节的重试装饰器原样搬来,第 8.2 节的 Semaphore 限流,第 8.1 节的选型矩阵决定并发模型:
import asyncio, aiohttp from functools import wraps def retry(times=3, backoff=0.5): def deco(fn): @wraps(fn) async def wrapper(*a, **kw): for attempt in range(1, times + 1): try: return await fn(*a, **kw) except (aiohttp.ClientError, asyncio.TimeoutError): if attempt == times: raise await asyncio.sleep(backoff * attempt) # 退避重试 return wrapper return deco async def crawl(urls, concurrency=5): sem, out = asyncio.Semaphore(concurrency), [] @retry(times=3) async def one(session, url): async with sem: # 限速:最多 5 个在飞 async with session.get(url, timeout=aiohttp.ClientTimeout(total=10)) as r: r.raise_for_status() return await r.text() async with aiohttp.ClientSession() as session: # 会话复用连接池 pages = await asyncio.gather(*(one(session, u) for u in urls), return_exceptions=True) # 单个失败不拖垮全场 return [(u, p if not isinstance(p, Exception) else None) for u, p in zip(urls, pages)]
并发模型的选择直接套 8.1 节的问句:几百到几万 URL 的 IO 密集采集 → asyncio(上面这版);几十个目标且逻辑简单 → 线程池更省事;需要对页面跑 JS 渲染(数据由脚本生成)→ 上无头浏览器(Playtree 一类的现代库,按事件驱动操控页面),注意它是真正的浏览器进程,资源开销大,只留给必须用的页面。
爬虫技术中立,行为有边界。动手前过一遍清单:
一句话版本:把对方服务器当合作方而不是矿场。
不碰网络的本机自动化同样是 Python 的主场,几个高频场景与趁手兵器:
from pathlib import Path import re for pdf in Path("下载").glob("*.pdf"): m = re.search(r"(\d{4})-(\d{2})", pdf.name) # 从文件名抽年月 if m: target = Path("归档") / m.group(1) / m.group(2) target.mkdir(parents=True, exist_ok=True) pdf.rename(target / pdf.name) # 一键归档
python -m(5.1 节),进程级用 APScheduler 一类库内嵌调度。长期跑的采集任务记得把日志落文件(6.1 节 with 写日志),出问题才有迹可循。⚠️ 常见坑:不开 timeout 的 requests 调用会永久挂起,拖死整个采集器;解析器写死层级路径,对方页面微调就全灭——尽量锚定语义特征(class/属性)而非深层嵌套;爬虫脚本里硬编码 Cookie,过期即全线失效。
💡 关键直觉:写爬虫前先问"这数据有 API/下载页吗"。爬页面永远是最后手段——正门永远比翻墙稳。
补一段实战认知:目标站点为什么能识别爬虫,以及正道何在。常见信号包括请求频率异常(短时间几百次)、行为指纹缺失(真人会加载静态资源、有滚动停留)、请求头特征(缺引用页、Cookie 不连贯)、IP 信誉(数据中心地址段)。对应地,采集侧的正道不是伪造得天衣无缝,而是降低存在感:限速到与人工浏览相当的量级、只取需要的数据、避开业务高峰、优先用官方数据接口或数据合作。被封锁(拒绝访问、验证码、封地址)时先自问是不是采得太狠了,而不是立刻上代理池硬刚——博弈升级对双方都是成本,而对方在自家场地上永远占优。长期看,稳定的数据获取靠的是合规与克制,技术只是手段。
最后一节进入机器学习与人工智能——数据分析的延长线上,那片生态最茂密的森林。