用 requests 抓到京东商品价格的爬虫那一刻,我才意识到网页上看到的技术东西和代码拿到的完全不是一回事。浏览器里显示 5999 元的网站手机,requests 返回的数据 HTML 里翻遍了也找不到这个数字。原因很简单,爬虫价格是技术 JavaScript 动态渲染上去的,静态请求拿到的网站只是骨架。这就是数据很多人学爬虫踩的第一个坑:以为把网页源码下载下来就能拿到所有数据。
Python 做爬虫,爬虫核心工具就那么几个。技术requests 负责发请求,网站BeautifulSoup 或 lxml 负责解析 HTML,数据遇到动态页面就上 Selenium 或 Playwright。爬虫更省事的技术方案是直接分析 XHR 请求,找到后端 API,网站用 requests 拿 JSON 数据。我抓某招聘网站岗位信息时,一开始用 Selenium 模拟浏览器翻页,速度慢得让人抓狂,一页要等五六秒。后来打开开发者工具,发现翻页本质上是向一个 API 发 POST 请求,参数就是页码和城市编码。改成直接请求 API 后,同样的数据,一秒钟能拿三页。
拿一个博客站点练手最合适。requests.get() 拿到 HTML 字符串,丢给 BeautifulSoup 解析,用 CSS 选择器定位标题、正文、发布时间。代码不超过二十行:
import requests from bs4 import BeautifulSoup resp = requests.get('https://example-blog.com/article/1', headers={ 'User-Agent': 'Mozilla/5.0'}) soup = BeautifulSoup(resp.text, 'lxml') title = soup.select_one('h1.post-title').text.strip() content = soup.select_one('div.post-content').text.strip() 注意 headers 里那个 User-Agent。不加的话,很多网站直接返回 403。这不是什么高深的反爬手段,就是服务器在检查请求头,看你是不是一个正常的浏览器。我见过有人写爬虫抓自己公司的内部 Wiki,死活抓不到,排查半天发现是运维在 Nginx 里配了 UA 黑名单,把 python-requests 默认的 UA 给拦了。
Selenium 能解决大部分动态加载问题,代价是慢。启动一个 Chrome 实例,内存吃掉几百兆,每步操作都要等元素加载。抓少量页面无所谓,上量就不行了。我的判断标准是:如果目标站点的数据接口不复杂,优先逆向 API;如果接口有加密签名或者请求参数很难构造,再退回 Selenium。
有一次帮朋友抓某电商平台的评论数据。接口需要携带一个 sign 参数,由时间戳、页码和一段密钥拼接后 MD5 得到。密钥藏在混淆过的 JS 文件里,格式化后翻了十几分钟才找到。这种场景下,与其硬啃 JS,不如直接用 Selenium 让浏览器自己算。虽然慢,但稳定。
网站的反爬策略五花八门。IP 频率限制、验证码、请求头校验、蜜罐链接、字体加密。字体加密比较有意思,某点评网站把数字映射成自定义字体,页面上显示“4.5”分,HTML 里却是乱码字符。你需要下载字体文件,用 fontTools 解析出映射关系,再替换回真实数字。第一次遇到时觉得挺巧妙,后来发现原理并不复杂,就是多了一步字体解析。
代理 IP 池是绕开频率限制的常规手段。免费代理基本不能用,存活时间短,速度慢,还经常返回假数据。付费代理质量参差不齐,我试过几家,稳定性最好的反而是按量计费的那种,虽然单价高,但省去了大量调试时间。写爬虫的时间成本也是成本。
爬虫本身不违法,但抓什么、怎么用、抓多少,都有讲究。公开数据、遵守 robots.txt、控制请求频率、不涉及个人隐私,这几条底线守住,一般不会出问题。我见过有人爬了某平台的用户手机号拿去卖,那就不是技术问题了。
实操层面,几个习惯能省不少事。请求之间加 time.sleep(random.uniform(1, 3)),模拟人类浏览节奏。用 logging 记录每次请求的状态码和耗时,出问题好排查。数据量大的话,边抓边存,别等全部抓完再写文件,中途挂了就全白干。
爬虫写多了会发现,技术本身不是最难的。难的是目标网站随时改版,今天能跑的代码明天可能就失效。所以代码要写得松耦合,解析逻辑和数据存储分开,改版时只需要调整解析部分。这大概就是爬虫工程师的日常,一半时间写代码,一半时间修代码。