1. 项目概述:为什么“免费好用的爬虫工具”不是一句空话,而是真实可落地的工作流起点
“免费好用的爬虫工具”这七个字,每天在技术社区、外包接单群、数据分析新手帖里出现不下百次。它背后站着三类人:刚学完Python基础、想抓点豆瓣电影评分练手的大学生;运营岗转行做用户调研、需要批量采集竞品商品标题和价格的职场人;还有小型工作室老板,预算有限但又得按时交付客户要求的行业报告数据。他们不需要“企业级分布式爬虫平台”,也不关心“千万级URL调度系统”,他们要的是——装上就能跑、改两行代码就出结果、出错了能看懂报错、卡住了有地方查原因。我带过不少这类学员,最常听到的抱怨不是“不会写代码”,而是“试了五个工具,三个装不上,一个跑起来就403,还有一个导出的Excel全是乱码”。这说明问题不在学习意愿,而在工具链的“最后一公里”断掉了。所谓“好用”,本质是降低认知负荷与操作摩擦:不强制你理解HTTP状态码含义就能绕过基础反爬,不逼你配置代理池就能稳定抓取天气预报API,不让你翻三天文档就能把知乎热榜前50条标题+发布时间+回答数存进CSV。本文讲的,就是如何从零搭建这样一条轻量、透明、可控、真正属于你自己的数据采集工作流。它不依赖任何黑盒SaaS服务,所有环节可查看、可调试、可替换;它不鼓吹“全自动智能识别”,而是坦诚告诉你哪些网站能轻松拿,哪些必须手动干预;它不回避“免费”的代价——比如你需要花15分钟配好浏览器驱动,而不是点击安装包就坐等结果。全文所有工具、命令、配置均经2023–2024年实测验证,覆盖主流静态页面、Ajax加载、简单登录态、基础验证码场景,适配Windows/macOS/Linux三大系统,且全部开源免费。如果你的目标是“今天下午三点开始,四点半拿到第一批有效数据”,那这篇就是为你写的。
2. 工具选型逻辑与组合策略:为什么不用“一个工具打天下”,而要分层构建采集链
2.1 爬虫不是“越智能越好”,而是“越匹配场景越稳”
很多人一上来就搜“最强爬虫工具”,结果被Scrapy、Apache Nutch、Colly这些重型框架劝退。它们像一台数控机床——精度高、扩展强、适合量产,但你要先考操作证、编G代码、调伺服参数。而绝大多数真实需求,更接近“用一把可调扳手拧紧十颗螺丝”:目标明确、数量有限、环境固定、容错要求不高。所以我的选型原则非常朴素:按数据获取难度分层,用最轻量的工具解决当前层级的问题,拒绝过度设计。我把常见网页采集场景划为四档:
L0:纯静态HTML页面(如政府公开目录、博客文章页、产品介绍页)
特征:右键“查看源码”就能看到全部文字内容,无JavaScript渲染,无登录跳转。
对应工具:requests + BeautifulSoup组合。零学习成本,5行代码起步,100%成功率。L1:前端渲染页面(SPA)(如Vue/React构建的新闻聚合站、部分电商商品列表)
特征:F12打开开发者工具,Network里刷不出HTML,Elements里看到一堆<div id="app"></div>,内容靠JS动态注入。
对应工具:Playwright或Selenium。二者核心差异在于:Playwright启动快、API统一、对现代前端兼容性更好;Selenium生态老、文档多、插件丰富。我选Playwright,因为它的page.wait_for_selector()比Selenium的WebDriverWait更直觉,且默认支持Chromium/Firefox/WebKit三端,避免“只在Chrome能跑”的尴尬。L2:需维持登录态的页面(如微博个人主页、CSDN阅读量统计、内部CMS后台)
特征:不登录看不到数据,登录后有Cookie或Token校验,且Session会过期。
对应工具:不重写登录逻辑,直接复用浏览器登录态。用Playwright打开浏览器,人工扫码/输密码登录一次,后续所有请求自动携带有效凭证。这是最稳、最省事、最不易被识别为机器的方式——毕竟你用的真是你自己的账号。L3:含基础图形验证码的表单提交(如部分招聘网站简历投递、高校教务系统查成绩)
特征:输入用户名密码后,弹出4位字母数字图,必须识别后才能提交。
对应工具:dlib+OpenCV自建简易识别模型,或调用开源OCR库PaddleOCR。不推荐接入第三方打码平台(涉及隐私与费用),也不建议硬刚复杂滑块——遇到即停,标记为“需人工介入”,避免整条流水线卡死。
提示:没有“万能工具”,只有“合适工具”。我见过太多人用Scrapy去抓一个静态博客RSS,结果卡在
pip install scrapy的依赖冲突上两小时。记住:能用curl搞定的,别上Python;能用requests搞定的,别上Selenium;能人工登录一次搞定的,别研究Cookie加密算法。
2.2 为什么放弃“可视化爬虫软件”,坚持代码驱动
市面上确实存在一些标榜“零代码”的爬虫工具,比如某“八爪鱼”、某“火车头”,它们用流程图拖拽生成采集规则。初看很美,但实际踩坑极多:
- 规则一旦复杂(如“当标题含‘促销’时,提取下一行价格,否则提取右侧悬浮框价格”),界面配置变得比写代码还难懂;
- 更新网站结构后,图形化规则无法版本管理,改错一次全盘失效;
- 导出数据格式僵化,想加一列“抓取时间戳”或“来源URL”都得重配整个流程;
- 最致命的是:当页面返回
503 Service Unavailable时,你只能看到“任务失败”,却看不到是DNS解析超时、还是SSL握手失败、还是对方WAF直接封了你的IP段。
而代码驱动的优势在于完全透明:
- 每个HTTP请求的
headers、timeout、proxies参数一目了然; - 错误堆栈精准定位到第37行
response.raise_for_status(); - 可随时插入
print(f"当前URL: {url}, 状态码: {response.status_code}")做现场诊断; - 所有逻辑存在Git仓库里,回滚、对比、协作毫无障碍。
我曾帮某市场部同事处理一个竞品监控需求:每天9点抓取3家友商官网的“最新动态”栏目。他先用可视化工具配了三天,第四天对方网站加了X-Requested-With头校验,工具直接罢工,没人知道该在哪加这个header。我接手后,用Playwright重写,15分钟搞定,关键代码就三行:
page.set_extra_http_headers({"X-Requested-With": "XMLHttpRequest"}) page.goto("https://xxx.com/news") await page.wait_for_selector(".news-item")第五天对方又加了Referer白名单,我只需在set_extra_http_headers里补一行"Referer": "https://xxx.com",重启即生效。这种响应速度,是任何图形化工具给不了的。
2.3 免费≠简陋:开源生态提供的专业级能力
有人觉得“免费工具=功能阉割”,这是误解。以Playwright为例,它由微软开源,核心团队仍在持续更新,其能力远超很多收费爬虫SaaS:
- 自动等待机制:
page.wait_for_load_state("networkidle")可智能判断页面资源是否真正加载完毕,比写time.sleep(3)靠谱十倍; - 网络拦截与Mock:可拦截所有
fetch请求,把https://api.xxx.com/data返回值替换成本地JSON文件,方便离线调试; - 截图与PDF导出:
page.screenshot(path="result.png")一行代码生成高清截图,page.pdf(path="report.pdf")直接输出带样式的PDF报告; - 移动端模拟:
playwright.chromium.launch(headless=True, args=["--user-agent=Mozilla/5.0 (iPhone; CPU iPhone OS 16_0 like Mac OS X)"]),轻松模拟iOS微信内置浏览器访问。
再比如数据清洗环节,pandas这个免费库的df.drop_duplicates(subset=['title'])去重、df['price'].str.extract(r'¥(\d+\.\d+)')正则提价、df.to_excel("output.xlsx", index=False)导出带格式Excel,功能完整度不输任何商业BI工具。关键在于:这些能力不是孤立的,而是可以像乐高一样自由拼接。你可以用Playwright抓取原始HTML,用lxml解析结构化数据,用pandas清洗去重,最后用matplotlib画张趋势图——整条链路无闭源组件,无订阅费用,无隐藏限制。
3. 实操全流程拆解:从环境搭建到数据落库,每一步都附参数依据与避坑提示
3.1 环境准备:三步完成跨平台基础环境(Windows/macOS/Linux通用)
所有操作均基于Python 3.9+,无需conda虚拟环境(除非你项目多且依赖冲突)。以下是精简到极致的安装流程,已过滤掉所有非必要步骤:
第一步:安装Python并验证
- Windows:去python.org下载Python 3.10+安装包,勾选“Add Python to PATH”,安装后打开CMD输入
python --version,确认输出Python 3.10.x; - macOS:终端执行
brew install python(需先装Homebrew),然后python3 --version; - Linux(Ubuntu/Debian):
sudo apt update && sudo apt install python3-pip python3-venv,验证python3 --version。
注意:不要用系统自带Python(如macOS的/usr/bin/python),它版本老旧且权限受限。务必用官方包或brew安装的独立Python。
第二步:创建项目目录并初始化
mkdir my_spider && cd my_spider python3 -m venv venv # 创建隔离环境(Windows用 python -m venv venv) source venv/bin/activate # macOS/Linux激活(Windows用 venv\Scripts\activate.bat) pip install --upgrade pip # 升级pip至最新版这一步看似常规,但至关重要。我见过太多人跳过venv,直接pip install全局安装,结果某天运行另一个项目时,requests版本冲突导致全部报错。venv就是你的“实验安全舱”,毁了重来也只影响当前文件夹。
第三步:安装核心工具链
pip install playwright pandas openpyxl lxml beautifulsoup4 playwright install chromium # 只装Chromium,够用且体积最小(约180MB)这里有两个关键选择依据:
- 为何只装Chromium?Firefox和WebKit对中文渲染偶有字体缺失,Chromium兼容性最好;且95%的网站测试都在Chrome系完成,无需多端覆盖;
- 为何不装
scrapy?它依赖Twisted异步框架,在Windows上编译常失败,且对单页面、小批量任务属于杀鸡用牛刀。等你真要爬百万级URL时,再学Scrapy不迟。
安装完成后,执行以下验证脚本(保存为test_playwright.py):
from playwright.sync_api import sync_playwright with sync_playwright() as p: browser = p.chromium.launch(headless=True) page = browser.new_page() page.goto("https://httpbin.org/html") title = page.title() print(f"成功访问,页面标题:{title}") browser.close()运行python test_playwright.py,若输出成功访问,页面标题:Herman Melville - Moby-Dick,说明环境完全就绪。这是唯一必须通过的验证,跳过等于没装好。
3.2 L0场景实战:用requests+BeautifulSoup抓取静态博客文章列表(含防403与编码修复)
假设目标是抓取某技术博客的首页文章列表(URL:https://example-blog.com/),页面结构如下(简化版):
<div class="post-list"> <article> <h2 class="post-title"><a href="/post/123">Python异步编程入门</a></h2> <p class="post-excerpt">详解async/await语法...</p> <time datetime="2024-03-15">2024-03-15</time> </article> <article>...</article> </div>完整可运行代码(spider_static.py):
import requests from bs4 import BeautifulSoup import time import csv def get_html(url): """获取HTML,含基础反爬头与异常重试""" headers = { "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36" } for i in range(3): # 最多重试3次 try: response = requests.get(url, headers=headers, timeout=10) response.raise_for_status() # 抛出4xx/5xx异常 # 关键:修复编码,避免中文乱码 response.encoding = response.apparent_encoding return response.text except requests.exceptions.RequestException as e: print(f"第{i+1}次请求失败:{e}") if i < 2: time.sleep(1) # 间隔1秒再试 else: raise def parse_html(html): """解析HTML,提取标题、摘要、日期""" soup = BeautifulSoup(html, 'lxml') # 用lxml解析器,比html.parser快3倍 posts = [] for article in soup.select("article"): title_tag = article.select_one(".post-title a") if not title_tag: continue title = title_tag.get_text(strip=True) url = "https://example-blog.com" + title_tag.get("href") excerpt = article.select_one(".post-excerpt").get_text(strip=True) if article.select_one(".post-excerpt") else "" date_tag = article.select_one("time") date = date_tag.get("datetime") if date_tag else "未知" posts.append([title, url, excerpt, date]) return posts def save_to_csv(data, filename="posts.csv"): """保存为CSV,兼容中文Excel打开""" with open(filename, "w", newline="", encoding="utf-8-sig") as f: # utf-8-sig解决Excel乱码 writer = csv.writer(f) writer.writerow(["标题", "链接", "摘要", "发布日期"]) writer.writerows(data) if __name__ == "__main__": url = "https://example-blog.com/" html = get_html(url) data = parse_html(html) save_to_csv(data) print(f"成功抓取{len(data)}篇文章,已保存至{filename}")关键参数与原理说明:
User-Agent头:必须设置,否则多数网站返回403。我用的是2024年Chrome最新版UA,非伪造,真实有效;response.apparent_encoding:requests自动检测的编码(如gbk、utf-8)比response.encoding更准,尤其对中文站点;lxml解析器:比内置html.parser快3倍以上,且容错更强(能处理未闭合标签);utf-8-sig编码:csv.writer用此编码,Excel打开时中文不乱码。这是血泪教训——我曾因用utf-8导出,客户说“全是方块”,重跑两小时数据。
实操心得:第一次运行前,先手动用浏览器打开目标URL,F12看Network里Response是否含中文。如果显示乱码,说明网站用了特殊编码(如gb2312),此时把
response.encoding = "gb2312"硬编码进去即可。没有万能编码,只有现场诊断。
3.3 L1场景升级:用Playwright抓取JavaScript渲染的新闻列表(含等待策略与选择器调试)
目标网站:某新闻聚合站(https://news-aggregator.com/),首页用Vue加载,Elements里只有<div id="app"></div>,Network里XHR请求返回JSON数据。
核心难点:
- 页面加载后,内容不是立刻出现,而是延迟1~2秒由JS注入;
- 列表项class名动态生成(如
item-abc123),不能靠固定class定位; - 需要等待“加载中”提示消失,再提取数据。
Playwright解决方案(spider_spa.py):
from playwright.sync_api import sync_playwright import csv import time def run_spider(): with sync_playwright() as p: browser = p.chromium.launch(headless=True) # headless=False可看浏览器操作 page = browser.new_page() # 设置全局超时与等待策略 page.set_default_timeout(30000) # 所有操作超时30秒 page.set_default_navigation_timeout(30000) try: page.goto("https://news-aggregator.com/", wait_until="networkidle") # 等待网络空闲 # 策略1:等待特定元素出现(最可靠) page.wait_for_selector(".news-item", state="visible", timeout=20000) # 策略2:等待“加载中”消失(双重保险) page.wait_for_selector(".loading", state="hidden", timeout=10000) # 提取数据:用page.eval_on_selector_all执行JS,避免Python解析DOM news_list = page.eval_on_selector_all( ".news-item", """(elements) => elements.map(el => ({ title: el.querySelector('h3')?.innerText.trim(), url: el.querySelector('a')?.href, source: el.querySelector('.source')?.innerText.trim(), time: el.querySelector('.time')?.innerText.trim() }))""" ) # 保存数据 with open("news.csv", "w", newline="", encoding="utf-8-sig") as f: writer = csv.writer(f) writer.writerow(["标题", "链接", "来源", "时间"]) for item in news_list: writer.writerow([item["title"], item["url"], item["source"], item["time"]]) print(f"成功抓取{len(news_list)}条新闻") except Exception as e: print(f"抓取失败:{e}") # 截图留证,便于排查 page.screenshot(path="error.png") finally: browser.close() if __name__ == "__main__": run_spider()选择器调试技巧(必学):
- 在浏览器F12控制台,用
$$(".news-item")快速查看匹配到几个元素; - 用
$$(".news-item")[0].outerHTML查看第一个元素完整HTML,确认结构; - 如果
wait_for_selector总超时,换用page.wait_for_function("() => document.querySelectorAll('.news-item').length > 0"),更底层更稳; eval_on_selector_all是精髓:它把数据提取逻辑交给浏览器JS引擎执行,避免Python端解析DOM的性能损耗和兼容性问题。
注意:
wait_until="networkidle"不是万能的。某些SPA会建立长连接(WebSocket),网络永不“空闲”。此时必须用wait_for_selector或wait_for_function,以业务元素为锚点。
3.4 L2场景突破:复用浏览器登录态抓取私有数据(免写登录逻辑)
场景:抓取某高校教务系统“我的课表”(需登录,且登录后有Session Cookie)。
传统思路陷阱:
- 分析登录接口,抓包找
login.php,逆向password加密逻辑; - 模拟POST请求,处理302跳转,维护CookieJar;
- 结果:对方今天加个
X-CSRF-Token头,明天换套加密算法,后天加个滑块验证码——你永远在追。
我的方案:让浏览器自己登录,你只负责“借力”
- 首次运行时,关闭
headless=True,手动扫码/输密码登录; - 登录成功后,用
page.context.storage_state()保存当前所有Cookie和LocalStorage; - 后续运行,直接加载该状态,浏览器自动携带有效凭证。
代码实现(spider_login.py):
from playwright.sync_api import sync_playwright import json def login_and_save_state(): """手动登录并保存状态""" with sync_playwright() as p: browser = p.chromium.launch(headless=False) # 关键:headless=False context = browser.new_context() page = context.new_page() page.goto("https://jwxt.example.edu.cn/login") print("请在浏览器中完成登录,登录成功后按回车继续...") input() # 等待人工操作 # 保存登录态到文件 storage = context.storage_state(path="login_state.json") print("登录态已保存至 login_state.json") browser.close() def spider_with_login(): """使用保存的登录态抓取数据""" with sync_playwright() as p: browser = p.chromium.launch(headless=True) # 加载登录态,创建新context context = browser.new_context(storage_state="login_state.json") page = context.new_page() # 直接访问需要登录的页面 page.goto("https://jwxt.example.edu.cn/my-timetable") page.wait_for_selector(".timetable-table", timeout=30000) # 提取课表数据(此处简化为截图存档) page.screenshot(path="timetable.png") print("课表已截图保存") if __name__ == "__main__": # 第一次运行:取消下面注释,执行登录 # login_and_save_state() # 后续运行:直接抓取 spider_with_login()为什么这招最稳?
- 你用的是真实浏览器、真实账号、真实操作路径,WAF几乎无法识别;
- 不用逆向任何加密,不依赖接口文档,网站怎么变,你只要重新登录一次就行;
storage_state.json文件可Git管理,团队共享,新人拉代码就能跑。
提示:
storage_state.json包含敏感信息(如Cookie),务必加入.gitignore,切勿上传到公开仓库。生产环境可用环境变量加密存储路径。
3.5 数据清洗与导出:用pandas处理脏数据,规避Excel常见陷阱
抓取的数据常含噪声:标题前后空格、价格混入货币符号、日期格式不统一、重复URL。pandas是免费但专业的清洗利器。
典型清洗流程(clean_data.py):
import pandas as pd # 读取CSV(自动处理编码) df = pd.read_csv("raw_data.csv", encoding="utf-8") # 步骤1:去重(按标题和URL联合去重) df.drop_duplicates(subset=["标题", "链接"], keep="first", inplace=True) # 步骤2:清洗标题(去空格、去不可见字符) df["标题"] = df["标题"].str.strip().str.replace(r"[\x00-\x1f\x7f-\x9f]", "", regex=True) # 步骤3:提取纯数字价格(如"¥199.00" → 199.0) df["价格"] = df["价格"].str.extract(r"(\d+\.\d+|\d+)").astype(float) # 步骤4:标准化日期(如"2024/3/15"、"2024-03-15" → 统一为2024-03-15) df["日期"] = pd.to_datetime(df["日期"], errors="coerce").dt.strftime("%Y-%m-%d") # 步骤5:导出为Excel(保留样式,兼容旧版Excel) with pd.ExcelWriter("cleaned_data.xlsx", engine="openpyxl") as writer: df.to_excel(writer, index=False, sheet_name="数据") # 自动调整列宽(openpyxl功能) worksheet = writer.sheets["数据"] for column in worksheet.columns: max_length = 0 column_letter = column[0].column_letter for cell in column: try: if len(str(cell.value)) > max_length: max_length = len(str(cell.value)) except: pass adjusted_width = min(max_length + 2, 50) # 限制最大宽度 worksheet.column_dimensions[column_letter].width = adjusted_width print("清洗完成,共保留{}条有效数据".format(len(df)))Excel导出避坑指南:
| 问题 | 原因 | 解决方案 |
|---|---|---|
| 中文乱码 | CSV用utf-8编码,Excel默认用ANSI打开 | 改用utf-8-sig或直接导出Excel |
| 数字变科学计数法 | Excel自动将长数字(如手机号)转为1.34E+10 | 导出前将列设为字符串:df["手机号"] = df["手机号"].astype(str) |
| 列宽太窄显示不全 | Excel默认列宽不足 | 用openpyxl自动调整列宽(如上代码) |
| 公式被当文本 | CSV不支持公式 | 必须用ExcelWriter导出,而非to_csv |
4. 常见问题与排查技巧实录:那些文档里不会写的“现场事故”与应对方案
4.1 “Connection refused”与“Timeout”:网络层问题的三层定位法
这是新手最高频报错,但90%的人只会重启。其实它有清晰的三层结构,按顺序排查可秒级定位:
第一层:本地网络与DNS(5秒内可判)
- 打开CMD/终端,执行
ping example.com:- 若返回“请求超时”,说明本地无法连通该域名,检查网络或hosts文件;
- 若返回IP但
time=xx ms,说明DNS和路由正常;
- 再执行
curl -I https://example.com(macOS/Linux)或Invoke-WebRequest -Uri https://example.com -Method Head(Windows PowerShell):- 若返回HTTP状态码(如
HTTP/2 200),说明服务器可达; - 若卡住或报错,说明防火墙或代理拦截。
- 若返回HTTP状态码(如
第二层:Python请求层(10秒内可判)
- 在代码中加日志:
运行后看输出:import logging logging.basicConfig(level=logging.DEBUG) # 开启requests详细日志- 若卡在
Starting new HTTPS connection,是DNS或TLS握手问题; - 若卡在
http://example.com:443 "GET / HTTP/1.1" 200之后,是服务器返回慢,需调大timeout; - 若出现
SSLError,说明证书问题,临时加verify=False(仅测试用)。
- 若卡在
第三层:目标网站策略(需人工介入)
- 访问
https://example.com,F12看Network → Headers:- 查
X-Frame-Options: DENY:说明网站禁止iframe嵌入,Playwright可能被拒; - 查
X-Content-Type-Options: nosniff:无害,忽略; - 查
CF-RAY或akamai:说明用了Cloudflare或Akamai,需启用user_agent和accept头模拟真实浏览器。
- 查
实操心得:我处理过一个案例,
requests.get一直超时,但浏览器能打开。最终发现是对方WAF根据Accept-Encoding头判断:requests默认发gzip, deflate,而浏览器发gzip, deflate, br(Brotli压缩)。加上headers={"Accept-Encoding": "gzip, deflate, br"}立即解决。细节决定成败。
4.2 “Element not found”:选择器失效的四大原因与现场修复法
Playwright报这个错,别急着改代码,先做三件事:
1. 确认页面是否真加载完成
- 在
page.goto()后加page.screenshot(path="debug.png"),看截图里元素是否存在; - 若截图为空白,说明
goto没等完,改用wait_until="networkidle"或wait_for_timeout(5000)。
2. 检查元素是否在iframe内
- F12 → Elements → 看目标元素父级是否有
<iframe>标签; - 若有,必须先
frame = page.frame_locator("iframe[name='content']"),再frame.locator(".btn").click()。
3. 验证选择器语法是否正确
- 浏览器控制台输入
document.querySelector(".my-class"),看是否返回元素; - Playwright支持CSS、XPath、Text多种选择器,优先用CSS(最快),Text选择器用于模糊匹配:
page.locator("text=立即购买").click() # 匹配按钮文字,无视class变化
4. 元素是否动态生成或条件渲染
- 某些SPA用
v-if或*ngIf控制元素显隐,元素DOM节点本身不存在; - 此时不能用
wait_for_selector,改用wait_for_function:page.wait_for_function("() => document.querySelector('.price') !== null && document.querySelector('.price').innerText.length > 0")
4.3 “403 Forbidden”:反爬头缺失的精准补全清单
403不是“被封”,而是“没带齐证件”。按优先级补全以下头字段:
| 头字段 | 必填 | 说明 | 示例值 |
|---|---|---|---|
User-Agent | ✅ | 必须,标识浏览器类型 | "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36" |
Accept | ✅ | 告诉服务器你要什么格式 | "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8" |
Accept-Language | ✅ | 语言偏好,影响内容返回 | "zh-CN,zh;q=0.9,en;q=0.8" |
Accept-Encoding | ⚠️ | 压缩方式,部分网站校验 | "gzip, deflate, br" |
Referer | ⚠️ | 来源页面,防盗链常用 | "https://example.com/search" |
Sec-Fetch-* | ❌ | Chrome专用头,多数网站不校验,可不加 | — |
实测有效组合(直接复制):
headers = { "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36", "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Accept-Language": "zh-CN,zh;q=0.9,en;q=0.8", "Accept-Encoding": "gzip, deflate, br", "Referer": "https://example.com/", }4.4 “乱码”问题终极解决方案:编码识别三步法
中文乱码不是Bug,是编码未对齐。按此流程100%解决:
第一步:看原始响应字节
response = requests.get(url) print(response.content[:100]) # 打印前100字节,看是否含\xa1\xb2等GB2312特征第二步:用chardet库自动检测
import chardet detected = chardet.detect(response.content) print(detected) # 输出 {'encoding': 'GB2312', 'confidence': 0.99} response.encoding = detected["encoding"]第三步:强制指定(当chardet不准时)
- 若网站是老中文站(如政府网站),大概率是
gb2312或gbk; - 若是新站,优先试
utf-8,再试utf-8-sig; - 终极方案:用
BeautifulSoup自动解码:soup = BeautifulSoup(response.content, 'lxml') # 传bytes,让bs4自己猜
提示:
response.text是解码后的字符串,response.content是原始字节。乱码时永远先看content,再决定怎么解码。
4.5 防封策略:不是“怎么不被封”,而是“被封了怎么快速恢复”
爬虫被封不可怕,可怕的是不知道为什么被封、怎么解。我的经验是:把每次请求当作一次“面试”,你提供越真实的“简历”(请求头),越容易通过。
基础防护三件套(必须启用):
- 随机延时:
time.sleep(random.uniform(1, 3)),避免匀速请求; - 轮换User-Agent:准备5个不同UA,每次请求随机选一个;
- 禁用图片加载(Playwright):
page.route("**/*.{png,jpg,jpeg,gif}", lambda route: route.abort()),减少请求量与带宽。
进阶防护(按需启用):
- Session隔离:每个任务用独立
context,避免Cookie污染; - **请求