ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

免费好用的爬虫工具:从零搭建轻量可控数据采集工作流

免费好用的爬虫工具:从零搭建轻量可控数据采集工作流

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头,明天换套加密算法,后天加个滑块验证码——你永远在追。

我的方案:让浏览器自己登录,你只负责“借力”

  1. 首次运行时,关闭headless=True,手动扫码/输密码登录;
  2. 登录成功后,用page.context.storage_state()保存当前所有Cookie和LocalStorage;
  3. 后续运行,直接加载该状态,浏览器自动携带有效凭证。

代码实现(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),说明服务器可达;
    • 若卡住或报错,说明防火墙或代理拦截。

第二层: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污染;
  • **请求
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进