用 Python 写你的第一个爬虫:小白也能轻松搞定数据抓取(超详细包含最新所有Python爬虫库的教程)

用 Python 写你的第一个爬虫:小白也能轻松搞定数据抓取(超详细包含最新所有Python爬虫库的教程)

用 Python 写你的第一个爬虫:小白也能轻松搞定数据抓取(超详细包含最新所有Python爬虫库的教程)

摘要本文是一篇面向爬虫爱好者的超详细 Python 爬虫入门教程,涵盖了从基础到进阶的所有关键技术点:使用 Requests 与 BeautifulSoup 实现静态网页数据抓取,运用 lxml、XPath、CSS 选择器等高效解析技术,深入 Scrapy 框架搭建分布式爬虫项目,掌握 Selenium 和 Playwright 浏览器自动化处理 JS 动态渲染,探索 aiohttp、HTTPX 异步爬虫提升并发性能,并结合代理 IP 池、User-Agent 伪装、验证码识别等反爬虫策略应对电商数据抓取、新闻数据爬取、社交媒体采集等场景。快速上手大规模爬虫项目,打造可扩展、高效稳定的数据抓取解决方案。

用 Python 写你的第一个爬虫:小白也能轻松搞定数据抓取(超详细包含最新所有Python爬虫库的教程)1. 前言在信息爆炸的时代,互联网早已成为最丰富、最便捷的数据来源。从电商平台的商品价格到新闻网站的最新动态,从社交媒体的热门话题到招聘网站的职位信息,只要你想得到,几乎都能通过爬虫从网页里“扒”出来。对于初学者而言,爬虫其实并不神秘:只要理解 HTTP、HTML 及基本的 Python 编程,就能快速入门。本教程面向“零基础”“小白”用户,讲解从最基本的抓取到进阶框架、异步、分布式再到反爬策略,逐步深入,手把手指导你搭建完整爬虫,并总结截至 2025 年最常用的 Python 爬虫库。

本教程特色

循序渐进:从最简单的 requests + BeautifulSoup 开始,到 Scrapy、Selenium、Playwright、异步爬虫,一步步掌握。超详细示例:每个工具/框架都配有完整可运行的示例代码,你可以直接复制、运行、观察。最新库盘点:整理并介绍了截至 2025 年所见的常用爬虫生态中的主流库,助你选对最合适的工具。反爬与实战:从简单的 User-Agent 伪装到代理 IP 池、验证码识别、分布式部署,多角度应对目标网站的各种反爬机制。 温馨提示:

本教程示例均基于 Python 3.8+,强烈建议使用 Python 3.10 或更高版本来获得更好的兼容性与性能。爬取网站数据时,请务必遵守目标网站的 robots.txt 以及相关法律法规,避免给他人服务器带来不必要的压力。本文所列“最新库”信息截止到 2024 年底,2025 年及以后的新库、新特性请结合官方文档或社区资源进行补充。2. 爬虫基础知识2.1 什么是爬虫?定义:爬虫(Web Crawler,也称 Spider、Bot)是一种通过程序自动访问网页,并将其中有用信息提取下来存储的数据采集工具。原理简述:爬虫首先向指定 URL 发起 HTTP 请求,获取网页源代码(HTML、JSON、图片等),再通过解析技术(如 XPath、CSS 选择器、正则)从源码中提取所需数据,最后将数据保存到文件或数据库中。2.2 爬虫的应用场景数据分析:电商价格监控、商品评论分析、竞品调研。舆情监控:社交媒体热搜、论坛帖子、新闻资讯统计。搜索引擎:Google、Bing、Baidu 等搜索引擎通过爬虫定期抓取网页进行索引。招聘信息采集:自动抓取招聘网站的岗位、薪资、公司信息。学术研究:论文元数据爬取、知识图谱构建等。内容聚合:如各类聚合网站把分散站点的文章集中到一个平台。2.3 爬虫基本流程确定目标 URL:明确要爬取的网页地址,可能是静态页面,也可能是动态加载。发送 HTTP 请求:通常使用 requests、httpx、aiohttp 等库向目标 URL 发送 GET、POST 请求,并获取响应。解析响应内容:响应可能是 HTML、JSON、XML、图片等,常用解析工具有 BeautifulSoup、lxml、parsel、PyQuery、正则表达式等。提取数据:根据标签名、属性、XPath、CSS Selector 等定位到目标内容,抽取文本或属性。数据处理与存储:将提取到的内容清洗、去重,然后保存到 CSV、JSON、SQLite、MySQL、MongoDB 等介质中。翻页/递归:如果需要多个页面的数据,就要分析翻页逻辑(URL 模板、Ajax 请求),循环执行请求与解析。异常处理与反爬对策:设置代理、随机 User-Agent、限速、IP 轮换,处理 HTTP 403、验证码、重定向等。2.4 需要注意的法律与伦理问题请求前务必查看目标站点的 robots.txt(通常在 https://example.com/robots.txt),遵从抓取规则;有些站点禁止大量抓取、禁止商业用途,在爬取前请阅读并遵守版权与隐私政策;不要对目标站点造成过大压力,建议设置合适的延时(time.sleep)、并发数限制;遵守爬虫与爬取数据后续处理相关法律法规,切勿用于违法用途。3. 开发环境准备3.1 安装 Python(建议 3.8 及以上)Windows:

前往 https://www.python.org/downloads 下载对应 3.8+ 的安装包,默认选中“Add Python 3.x to PATH”,点击“Install Now”。

安装完成后,打开命令行(Win + R → 输入 cmd → 回车),执行:

代码语言:javascript代码运行次数:0运行复制python --version

pip --version确认 Python 与 pip 已成功安装。

macOS:

建议使用 Homebrew 安装:

代码语言:javascript代码运行次数:0运行复制brew install python@3.10安装完成后,执行:

代码语言:javascript代码运行次数:0运行复制python3 --version

pip3 --version确认无误后即可。

Linux (Ubuntu/Debian 系):

代码语言:javascript代码运行次数:0运行复制sudo apt update

sudo apt install python3 python3-pip python3-venv -y执行:

代码语言:javascript代码运行次数:0运行复制python3 --version

pip3 --version即可确认。

提示:如果你机器上同时安装了 Python 2.x 和 Python 3.x,可能需要使用 python3、pip3 来替代 python、pip。

3.2 创建虚拟环境并激活为了避免全局依赖冲突,强烈建议为每个爬虫项目创建独立的虚拟环境:

代码语言:javascript代码运行次数:0运行复制# 进入项目根目录

mkdir my_spider && cd my_spider

# 在项目目录下创建虚拟环境(python3 -m venv venv 或 python -m venv venv)

python3 -m venv venv

# 激活虚拟环境

# Windows:

venv\Scripts\activate

# macOS/Linux:

source venv/bin/activate激活后,终端左侧会显示 (venv),此时安装的所有包都只作用于该环境。

3.3 常用开发工具推荐 IDE/编辑器:

PyCharm Community / Professional:功能强大,集成测试、版本管理。VS Code:轻量且插件丰富,适合快速编辑。Sublime Text:轻量,启动快;对于小脚本很方便。 调试工具:

VS Code/PyCharm 自带的调试器,可以单步、断点调试。对于命令行脚本,也可以使用 pdb。 版本管理:

Git + VS Code / PyCharm Git 插件,实现代码托管与协作。将项目托管到 GitHub/Gitee 等。 其他辅助:

Postman / Insomnia:用于模拟 HTTP 请求、查看响应头;Charles / Fiddler:抓包工具,可调试 AJAX 请求、Cookie、headers 等。4. 基础篇:用 Requests + BeautifulSoup 做简单爬虫4.1 安装必要库在虚拟环境中,执行:

代码语言:javascript代码运行次数:0运行复制pip install requests beautifulsoup4 lxmlrequests:Python 最常用的 HTTP 库,用于发送 GET/POST 请求。beautifulsoup4:常见的 HTML/XML 解析库,入门简单。lxml:速度快、功能强大的解析器,供 BeautifulSoup 使用。4.2 认识 HTTP 请求与响应 HTTP 请求:由方法(GET、POST、PUT 等)、URL、请求头(Headers)、请求体(Body)等组成。

HTTP 响应:包含状态码(200、404、500 等)、响应头、响应体(通常为 HTML、JSON、图片、文件等)。

Requests 常用参数:

url:请求地址。params:URL 参数(字典/字符串)。headers:自定义请求头(例如 User-Agent、Referer、Cookie)。data / json:POST 请求时发送的表单或 JSON 数据。timeout:超时时间(秒),防止请求一直卡住。proxies:配置代理(详见后文)。示例:

代码语言:javascript代码运行次数:0运行复制import requests

url = 'https://httpbin.org/get'

params = {'q': 'python 爬虫', 'page': 1}

headers = {

'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) ...'

}

response = requests.get(url, params=params, headers=headers, timeout=10)

print(response.status_code) # 打印状态码,例如 200

print(response.encoding) # 编码,例如 'utf-8'

print(response.text[:200]) # 前 200 字符4.3 编写第一个爬虫:抓取网页标题下面以爬取「https://www.example.com」网页标题为例,演示最简单的流程:

代码语言:javascript代码运行次数:0运行复制# file: simple_spider.py

import requests

from bs4 import BeautifulSoup

def fetch_title(url):

try:

# 1. 发送 GET 请求

headers = {

'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) ...'

}

response = requests.get(url, headers=headers, timeout=10)

response.raise_for_status() # 如果状态码不是 200,引发 HTTPError

# 2. 设置正确的编码

response.encoding = response.apparent_encoding

# 3. 解析 HTML

soup = BeautifulSoup(response.text, 'lxml')

# 4. 提取 标签内容</p> <p>title_tag = soup.find('title')</p> <p>if title_tag:</p> <p>return title_tag.get_text().strip()</p> <p>else:</p> <p>return '未找到 title 标签'</p> <p>except Exception as e:</p> <p>return f'抓取失败:{e}'</p> <p>if __name__ == '__main__':</p> <p>url = 'https://www.example.com'</p> <p>title = fetch_title(url)</p> <p>print(f'网页标题:{title}')运行结果示例:</p> <p>代码语言:javascript代码运行次数:0运行复制(venv) $ python simple_spider.py</p> <p>网页标题:Example Domain4.4 解析HTML:BeautifulSoup 用法详解BeautifulSoup 库使用简单,常用方法如下:</p> <p>创建对象</p> <p>代码语言:javascript代码运行次数:0运行复制soup = BeautifulSoup(html_text, 'lxml') # 或 'html.parser'查找单个节点</p> <p>soup.find(tag_name, attrs={}, recursive=True, text=None, **kwargs)示例:soup.find('div', class_='content')可以使用 attrs={'class': 'foo', 'id': 'bar'} 精确定位。查找所有节点</p> <p>soup.find_all(tag_name, attrs={}, limit=None, **kwargs)示例:soup.find_all('a', href=True) 返回所有带 href 的链接。CSS 选择器</p> <p>soup.select('div.content > ul li a'),返回列表。支持 id(#id)、class(.class)、属性([attr=value])等。获取属性或文本</p> <p>node.get('href'):拿属性值;node['href']:同上,但如果属性不存在会抛异常;node.get_text(strip=True):获取节点文本,并去除前后空白;node.text:获取节点及子节点合并文本。常用属性</p> <p>soup.title / soup.title.string / soup.title.textsoup.body / soup.head / soup.a / soup.div 等快捷属性。示例:提取列表页所有文章链接</p> <p>代码语言:javascript代码运行次数:0运行复制html = response.text</p> <p>soup = BeautifulSoup(html, 'lxml')</p> <p># 假设每篇文章链接都在 <h2 class="post-title"><a href="...">...</a></h2></p> <p>for h2 in soup.find_all('h2', class_='post-title'):</p> <p>a_tag = h2.find('a')</p> <p>title = a_tag.get_text(strip=True)</p> <p>link = a_tag['href']</p> <p>print(title, link)4.5 文件存储:将抓到的数据保存为 CSV/JSONCSV 格式</p> <p>代码语言:javascript代码运行次数:0运行复制import csv</p> <p>data = [</p> <p>{'title': '第一篇', 'url': 'https://...'},</p> <p>{'title': '第二篇', 'url': 'https://...'},</p> <p># ...</p> <p>]</p> <p>with open('result.csv', mode='w', newline='', encoding='utf-8-sig') as f:</p> <p>fieldnames = ['title', 'url']</p> <p>writer = csv.DictWriter(f, fieldnames=fieldnames)</p> <p>writer.writeheader()</p> <p>for item in data:</p> <p>writer.writerow(item)encoding='utf-8-sig' 能兼容 Excel 打开时不出现乱码。JSON 格式</p> <p>代码语言:javascript代码运行次数:0运行复制import json</p> <p>data = [</p> <p>{'title': '第一篇', 'url': 'https://...'},</p> <p>{'title': '第二篇', 'url': 'https://...'},</p> <p># ...</p> <p>]</p> <p>with open('result.json', 'w', encoding='utf-8') as f:</p> <p>json.dump(data, f, ensure_ascii=False, indent=4)SQLite 存储(适合小规模项目)</p> <p>代码语言:javascript代码运行次数:0运行复制import sqlite3</p> <p>conn = sqlite3.connect('spider.db')</p> <p>cursor = conn.cursor()</p> <p># 创建表(如果不存在)</p> <p>cursor.execute('''</p> <p>CREATE TABLE IF NOT EXISTS articles (</p> <p>id INTEGER PRIMARY KEY AUTOINCREMENT,</p> <p>title TEXT,</p> <p>url TEXT UNIQUE</p> <p>);</p> <p>''')</p> <p># 插入数据</p> <p>items = [</p> <p>('第一篇', 'https://...'),</p> <p>('第二篇', 'https://...'),</p> <p>]</p> <p>for title, url in items:</p> <p>try:</p> <p>cursor.execute('INSERT INTO articles (title, url) VALUES (?, ?)', (title, url))</p> <p>except sqlite3.IntegrityError:</p> <p>pass # URL 已存在就跳过</p> <p>conn.commit()</p> <p>conn.close()4.6 常见反爬措施及应对策略User-Agent 检测</p> <p>默认 requests 的 User-Agent 大多被识别为“爬虫”,容易被屏蔽。应用:在请求头中随机选用常见浏览器 User-Agent。代码语言:javascript代码运行次数:0运行复制import random</p> <p>USER_AGENTS = [</p> <p>'Mozilla/5.0 ... Chrome/100.0.4896.127 ...',</p> <p>'Mozilla/5.0 ... Firefox/110.0 ...',</p> <p>'Mozilla/5.0 ... Safari/605.1.15 ...',</p> <p># 更多可从网上获取</p> <p>]</p> <p>headers = {'User-Agent': random.choice(USER_AGENTS)}</p> <p>response = requests.get(url, headers=headers)IP 限制</p> <p>如果同一 IP 在短时间内发起大量请求,服务器可能会封禁或返回 403。应对:使用代理池(详见第 11 节),定期更换 IP。Cookie 验证</p> <p>某些网站登录后才能访问完整内容,需要先模拟登录获取 Cookie,再在后续请求中带上。用 requests.Session() 管理会话,同一 Session 自动保存并发送 Cookie。代码语言:javascript代码运行次数:0运行复制import requests</p> <p>session = requests.Session()</p> <p>login_data = {'username': 'xxx', 'password': 'xxx'}</p> <p>session.post('https://example.com/login', data=login_data)</p> <p># 登录成功后,session 自动保存了 Cookie</p> <p>response = session.get('https://example.com/protected-page')验证码</p> <p>简易验证码有时可通过 OCR 自动识别,但复杂图片验证码需要专门打码平台或人工识别。在入门阶段,尽量选择不需要验证码或抢先获取 API。AJAX / 动态渲染</p> <p>如果页面数据是通过 JavaScript 动态加载,直接用 requests 只能获取静态 HTML。应用:可分析 AJAX 请求接口(Network 面板),直接请求接口返回的 JSON;或使用浏览器自动化工具(Selenium/Playwright)模拟浏览器渲染。5. 进阶篇:更强大的解析工具虽然 BeautifulSoup 足以应付大部分新手场景,但当你遇到结构复杂、嵌套多、或需要批量高效提取时,下面这些工具会更适合。</p> <p>5.1 lxml (XPath)特点:基于 C 语言实现,解析速度快,支持标准的 XPath 查询。</p> <p>安装:</p> <p>代码语言:javascript代码运行次数:0运行复制pip install lxml示例:</p> <p>代码语言:javascript代码运行次数:0运行复制from lxml import etree</p> <p>html = '''<html><body></p> <p><div class="post"><h2><a href="/p1">文章A</a></h2></div></p> <p><div class="post"><h2><a href="/p2">文章B</a></h2></div></p> <p></body></html>'''</p> <p># 1. 将文本转换为 Element 对象</p> <p>tree = etree.HTML(html)</p> <p># 2. 使用 XPath 语法提取所有链接文本和 href</p> <p>titles = tree.xpath('//div[@class="post"]/h2/a/text()')</p> <p>links = tree.xpath('//div[@class="post"]/h2/a/@href')</p> <p>for t, l in zip(titles, links):</p> <p>print(t, l)</p> <p># 输出:</p> <p># 文章A /p1</p> <p># 文章B /p2常见 XPath 语法:</p> <p>//tag[@attr="value"]:查找所有符合条件的 tag。text():获取文本节点;@href:获取属性值;//div//a:查找 div 下所有后代中的 a;//ul/li[1]:查找第一个 li;contains(@class, "foo"):class 中包含 foo 的元素。5.2 parsel(Scrapy 内置的解析器)特点:Scrapy 自带的一套基于 Css/XPath 的快速解析工具,接口与 lxml 类似,但更贴合 Scrapy 的数据提取习惯。</p> <p>安装:</p> <p>代码语言:javascript代码运行次数:0运行复制pip install parsel示例:</p> <p>代码语言:javascript代码运行次数:0运行复制from parsel import Selector</p> <p>html = '''<ul></p> <p><li class="item"><a href="/a1">Item1</a></li></p> <p><li class="item"><a href="/a2">Item2</a></li></p> <p></ul>'''</p> <p>sel = Selector(text=html)</p> <p># 使用 CSS 选择器</p> <p>for item in sel.css('li.item'):</p> <p>title = item.css('a::text').get()</p> <p>link = item.css('a::attr(href)').get()</p> <p>print(title, link)</p> <p># 使用 XPath</p> <p>for item in sel.xpath('//li[@class="item"]'):</p> <p>title = item.xpath('./a/text()').get()</p> <p>link = item.xpath('./a/@href').get()</p> <p>print(title, link)parsel.Selector 对象在 Scrapy 中经常用到,直接拿过来在项目外部也能用。</p> <p>5.3 PyQuery(类似 jQuery 的解析方式)特点:接口风格类似 jQuery,习惯了前端的同学会很快上手。</p> <p>安装:</p> <p>代码语言:javascript代码运行次数:0运行复制pip install pyquery示例:</p> <p>代码语言:javascript代码运行次数:0运行复制from pyquery import PyQuery as pq</p> <p>html = '''<div id="posts"></p> <p><h2><a href="/x1">新闻X1</a></h2></p> <p><h2><a href="/x2">新闻X2</a></h2></p> <p></div>'''</p> <p>doc = pq(html)</p> <p># 通过标签/ID/css 选择器定位</p> <p>for item in doc('#posts h2'):</p> <p># item 是 lxml 的 Element,需要再次包装</p> <p>a = pq(item).find('a')</p> <p>title = a.text()</p> <p>url = a.attr('href')</p> <p>print(title, url)PyQuery 内部使用 lxml 作为解析器,速度不逊于直接调用 lxml。</p> <p>5.4 正则表达式在爬虫中的应用正则并不是万能的 HTML 解析方案,但在提取简单规则(如邮箱、电话号码、特定模式字符串)时非常方便。</p> <p>在爬虫中,可先用 BeautifulSoup/lxml 找到相应的大块内容,再对内容字符串用正则提取。</p> <p>示例:</p> <p>代码语言:javascript代码运行次数:0运行复制import re</p> <p>from bs4 import BeautifulSoup</p> <p>html = '''<div class="info"></p> <p>联系邮箱:abc@example.com</p> <p>联系电话:123-4567-890</p> <p></div>'''</p> <p>soup = BeautifulSoup(html, 'lxml')</p> <p>info = soup.find('div', class_='info').get_text()</p> <p># 匹配邮箱</p> <p>email_pattern = r'[\w\.-]+@[\w\.-]+'</p> <p>emails = re.findall(email_pattern, info)</p> <p>print('邮箱:', emails)</p> <p># 匹配电话号码</p> <p>phone_pattern = r'\d{3}-\d{4}-\d{3,4}'</p> <p>phones = re.findall(phone_pattern, info)</p> <p>print('电话:', phones)6. 框架篇:Scrapy 全面入门如果你想快速搭建一个可维护、可扩展的爬虫项目,Scrapy 是 Python 爬虫生态中最成熟、最流行的爬虫框架之一。</p> <p>6.1 Scrapy 简介 Scrapy:一个专门为大规模网络爬取与信息提取设计的开源框架,具有高性能、多并发、支持分布式、内置各种中间件与管道。</p> <p>适用场景:</p> <p>大规模爬取同类型大量网页。对页面进行复杂数据清洗、去重、存储。需要高度定制化中间件或扩展时。6.2 安装与项目结构安装 Scrapy:</p> <p>代码语言:javascript代码运行次数:0运行复制pip install scrapy创建 Scrapy 项目:</p> <p>代码语言:javascript代码运行次数:0运行复制scrapy startproject myproject项目目录结构(示例):</p> <p>代码语言:javascript代码运行次数:0运行复制myproject/</p> <p>scrapy.cfg # 部署时使用的配置文件</p> <p>myproject/ # 项目 Python 模块</p> <p>__init__.py</p> <p>items.py # 定义数据模型(Item)</p> <p>middlewares.py # 自定义中间件</p> <p>pipelines.py # 数据处理与存储 Pipeline</p> <p>settings.py # Scrapy 全局配置</p> <p>spiders/ # 各种爬虫文件放在这里</p> <p>__init__.py</p> <p>example_spider.py6.3 编写第一个 Scrapy 爬虫 Spider假设我们要爬去 quotes.toscrape.com 网站上所有名言及作者:</p> <p>在 myproject/spiders/ 下新建 quotes_spider.py:</p> <p>代码语言:javascript代码运行次数:0运行复制import scrapy</p> <p>from myproject.items import MyprojectItem</p> <p>class QuotesSpider(scrapy.Spider):</p> <p>name = 'quotes' # 爬虫名,运行时指定</p> <p>allowed_domains = ['quotes.toscrape.com']</p> <p>start_urls = ['https://quotes.toscrape.com/']</p> <p>def parse(self, response):</p> <p># 提取每个名言块</p> <p>for quote in response.css('div.quote'):</p> <p>item = MyprojectItem()</p> <p>item['text'] = quote.css('span.text::text').get()</p> <p>item['author'] = quote.css('small.author::text').get()</p> <p>item['tags'] = quote.css('div.tags a.tag::text').getall()</p> <p>yield item</p> <p># 翻页:获取下一页链接并递归</p> <p>next_page = response.css('li.next a::attr(href)').get()</p> <p>if next_page:</p> <p>yield response.follow(next_page, callback=self.parse)定义 Item 模型 (myproject/items.py):</p> <p>代码语言:javascript代码运行次数:0运行复制import scrapy</p> <p>class MyprojectItem(scrapy.Item):</p> <p>text = scrapy.Field()</p> <p>author = scrapy.Field()</p> <p>tags = scrapy.Field()配置数据存储 Pipeline(可选存储到 JSON/CSV/数据库),如在 myproject/pipelines.py:</p> <p>代码语言:javascript代码运行次数:0运行复制import json</p> <p>class JsonWriterPipeline:</p> <p>def open_spider(self, spider):</p> <p>self.file = open('quotes.json', 'w', encoding='utf-8')</p> <p>self.file.write('[\n')</p> <p>def close_spider(self, spider):</p> <p>self.file.write('\n]')</p> <p>self.file.close()</p> <p>def process_item(self, item, spider):</p> <p>line = json.dumps(dict(item), ensure_ascii=False)</p> <p>self.file.write(line + ',\n')</p> <p>return item并在 settings.py 中启用:</p> <p>代码语言:javascript代码运行次数:0运行复制ITEM_PIPELINES = {</p> <p>'myproject.pipelines.JsonWriterPipeline': 300,</p> <p>}运行爬虫:</p> <p>代码语言:javascript代码运行次数:0运行复制scrapy crawl quotes运行后,会在项目根目录生成 quotes.json,其中包含抓取到的所有名言数据。</p> <p>6.4 Item、Pipeline、Settings 详解Items (items.py):定义要提取的数据结构与字段,相当于“数据模型”。Spiders (spiders/xxx.py):每个 spider 文件对应一个任务,可接收 start_urls、allowed_domains、parse() 回调等。可自定义不同的回调函数来解析不同页面。Pipelines (pipelines.py):处理从 Spider 返回的 Item,常见操作包括数据清洗(去重、格式化)、存储(写入 JSON/CSV、入库)、下载附件等。Settings (settings.py):全局配置文件,包含并发数(CONCURRENT_REQUESTS)、下载延时(DOWNLOAD_DELAY)、中间件配置、管道配置、User-Agent 等。常见 Settings 配置示例:</p> <p>代码语言:javascript代码运行次数:0运行复制# settings.py(只列部分)</p> <p>BOT_NAME = 'myproject'</p> <p>SPIDER_MODULES = ['myproject.spiders']</p> <p>NEWSPIDER_MODULE = 'myproject.spiders'</p> <p># 遵循 robots 协议</p> <p>ROBOTSTXT_OBEY = True</p> <p># 并发请求数(默认 16)</p> <p>CONCURRENT_REQUESTS = 8</p> <p># 下载延时(秒),防止对目标站造成过大压力</p> <p>DOWNLOAD_DELAY = 1</p> <p># 配置 User-Agent</p> <p>DEFAULT_REQUEST_HEADERS = {</p> <p>'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) ...',</p> <p>}</p> <p># 启用 Pipeline</p> <p>ITEM_PIPELINES = {</p> <p>'myproject.pipelines.JsonWriterPipeline': 300,</p> <p>}</p> <p># 启用或禁用中间件、扩展、管道等</p> <p>DOWNLOADER_MIDDLEWARES = {</p> <p># 'myproject.middlewares.SomeDownloaderMiddleware': 543,</p> <p>}</p> <p># 日志等级</p> <p>LOG_LEVEL = 'INFO'6.5 Scrapy Shell 在线调试Scrapy 提供了 scrapy shell <URL> 命令,可以快速测试 XPath、CSS 选择器。</p> <p>代码语言:javascript代码运行次数:0运行复制scrapy shell 'https://quotes.toscrape.com/'进入 shell 后,你可以执行:</p> <p>代码语言:javascript代码运行次数:0运行复制>>> response.status</p> <p>200</p> <p>>>> response.css('div.quote span.text::text').getall()</p> <p>['“The world as we have created it is a process of our thinking. It cannot be changed without changing our thinking.”', ...]</p> <p>>>> response.xpath('//div[@class="quote"]/span[@class="text"]/text()').getall()Shell 模式下,你可以快速试错、验证提取逻辑,比写完整 Spider 再跑要高效很多。</p> <p>6.6 分布式与多线程:Scrapy 爬虫并发配置并发请求数:在 settings.py 中设置 CONCURRENT_REQUESTS(默认 16);单域名并发:CONCURRENT_REQUESTS_PER_DOMAIN(默认 8);单 IP 并发:CONCURRENT_REQUESTS_PER_IP;下载延时:DOWNLOAD_DELAY(默认 0);自动限速:AUTOTHROTTLE_ENABLED = True,配合 AUTOTHROTTLE_START_DELAY、AUTOTHROTTLE_MAX_DELAY 等。并行请求:Scrapy 内部使用 Twisted 异步网络库实现高并发,单机即可轻松处理成千上万请求。6.7 Scrapy 中间件与扩展(Downloader Middleware、Downloader Handler)Downloader Middleware:位于 Scrapy 引擎与下载器之间,可控制请求/响应,常用于:</p> <p>动态设置 User-Agent、Proxy;拦截并修改请求/响应头;处理重试(Retry)、重定向(Redirect)等。示例:随机 User-Agent Middleware</p> <p>代码语言:javascript代码运行次数:0运行复制# myproject/middlewares.py</p> <p>import random</p> <p>from scrapy import signals</p> <p>class RandomUserAgentMiddleware:</p> <p>def __init__(self, user_agents):</p> <p>self.user_agents = user_agents</p> <p>@classmethod</p> <p>def from_crawler(cls, crawler):</p> <p>return cls(</p> <p>user_agents=crawler.settings.get('USER_AGENTS_LIST')</p> <p>)</p> <p>def process_request(self, request, spider):</p> <p>ua = random.choice(self.user_agents)</p> <p>request.headers.setdefault('User-Agent', ua)并在 settings.py 中配置:</p> <p>代码语言:javascript代码运行次数:0运行复制USER_AGENTS_LIST = [</p> <p>'Mozilla/5.0 ... Chrome/100.0 ...',</p> <p>'Mozilla/5.0 ... Firefox/110.0 ...',</p> <p># 更多 User-Agent</p> <p>]</p> <p>DOWNLOADER_MIDDLEWARES = {</p> <p>'myproject.middlewares.RandomUserAgentMiddleware': 400,</p> <p>'scrapy.downloadermiddlewares.useragent.UserAgentMiddleware': None,</p> <p>}Downloader Handler:更底层的接口,一般不常用,Scrapy 已提供 HttpDownloadHandler、S3DownloadHandler 等。</p> <p>7. 动态内容爬取:Selenium 与 Playwright当目标网页内容依赖 JavaScript 动态渲染时,单纯用 requests 或 Scrapy 获取到的 HTML 往往不包含最终可视化的数据。此时可以使用“浏览器自动化”工具,让其像真实浏览器一样加载页面,再提取渲染后的内容。</p> <p>7.1 为什么需要浏览器自动化? 许多现代网站(尤其是单页应用 SPA)使用 React、Vue、Angular 等框架,通过 AJAX 或 API 获取数据并在前端渲染,直接请求 URL 只能拿到空白或框架代码。</p> <p>浏览器自动化可以:</p> <p>启动一个真实或无头浏览器实例;访问页面,等待 JavaScript 执行完成;拿到渲染完毕的 DOM,然后再用解析库提取。7.2 Selenium 基础用法安装:</p> <p>代码语言:javascript代码运行次数:0运行复制pip install selenium下载 WebDriver(以 Chrome 为例):</p> <p>前往 ChromeDriver 下载页面 ,下载与本地 Chrome 版本相匹配的 chromedriver。将 chromedriver 放置在系统 PATH 下,或在代码中指定路径。示例:抓取动态网页内容</p> <p>代码语言:javascript代码运行次数:0运行复制from selenium import webdriver</p> <p>from selenium.webdriver.chrome.service import Service as ChromeService</p> <p>from selenium.webdriver.common.by import By</p> <p>from selenium.webdriver.chrome.options import Options</p> <p>import time</p> <p># 1. 配置 Chrome 选项</p> <p>chrome_options = Options()</p> <p>chrome_options.add_argument('--headless') # 无界面模式</p> <p>chrome_options.add_argument('--no-sandbox')</p> <p>chrome_options.add_argument('--disable-gpu')</p> <p># 2. 指定 chromedriver 路径或直接放到 PATH 中</p> <p>service = ChromeService(executable_path='path/to/chromedriver')</p> <p># 3. 创建 WebDriver</p> <p>driver = webdriver.Chrome(service=service, options=chrome_options)</p> <p>try:</p> <p># 4. 打开页面</p> <p>driver.get('https://quotes.toscrape.com/js/') # 这是一个 JavaScript 渲染的示例</p> <p># 5. 等待 JS 渲染,最简单的方式:time.sleep(建议改用显式/隐式等待)</p> <p>time.sleep(2)</p> <p># 6. 提取渲染后的 HTML</p> <p>html = driver.page_source</p> <p># 7. 交给 BeautifulSoup 或 lxml 解析</p> <p>from bs4 import BeautifulSoup</p> <p>soup = BeautifulSoup(html, 'lxml')</p> <p>for quote in soup.css('div.quote'):</p> <p>text = quote.find('span', class_='text').get_text()</p> <p>author = quote.find('small', class_='author').get_text()</p> <p>print(text, author)</p> <p>finally:</p> <p>driver.quit()显式等待与隐式等待</p> <p>隐式等待:driver.implicitly_wait(10),在寻找元素时最长等待 10 秒;</p> <p>显式等待:使用 WebDriverWait 与 ExpectedConditions,例如:</p> <p>代码语言:javascript代码运行次数:0运行复制from selenium.webdriver.support.ui import WebDriverWait</p> <p>from selenium.webdriver.support import expected_conditions as EC</p> <p>element = WebDriverWait(driver, 10).until(</p> <p>EC.presence_of_element_located((By.CSS_SELECTOR, 'div.quote'))</p> <p>)7.3 Playwright for Python(更快更轻量)Playwright:由微软维护、继承自 Puppeteer 的跨浏览器自动化库,支持 Chromium、Firefox、WebKit,无需单独下载 WebDriver。</p> <p>优点:启动速度快、API 简洁、并发控制更灵活。</p> <p>安装:</p> <p>代码语言:javascript代码运行次数:0运行复制pip install playwright</p> <p># 安装浏览器内核(只需第一次执行)</p> <p>playwright install示例:抓取动态内容</p> <p>代码语言:javascript代码运行次数:0运行复制import asyncio</p> <p>from playwright.async_api import async_playwright</p> <p>from bs4 import BeautifulSoup</p> <p>async def main():</p> <p>async with async_playwright() as p:</p> <p>browser = await p.chromium.launch(headless=True)</p> <p>page = await browser.new_page()</p> <p>await page.goto('https://quotes.toscrape.com/js/')</p> <p># 可选:等待某个元素加载完成</p> <p>await page.wait_for_selector('div.quote')</p> <p>content = await page.content() # 获取渲染后的 HTML</p> <p>await browser.close()</p> <p># 交给 BeautifulSoup 解析</p> <p>soup = BeautifulSoup(content, 'lxml')</p> <p>for quote in soup.select('div.quote'):</p> <p>text = quote.select_one('span.text').get_text()</p> <p>author = quote.select_one('small.author').get_text()</p> <p>print(text, author)</p> <p>if __name__ == '__main__':</p> <p>asyncio.run(main())同步版 Playwright</p> <p>如果你不想使用异步,也可以借助 sync_api:</p> <p>代码语言:javascript代码运行次数:0运行复制from playwright.sync_api import sync_playwright</p> <p>from bs4 import BeautifulSoup</p> <p>def main():</p> <p>with sync_playwright() as p:</p> <p>browser = p.chromium.launch(headless=True)</p> <p>page = browser.new_page()</p> <p>page.goto('https://quotes.toscrape.com/js/')</p> <p>page.wait_for_selector('div.quote')</p> <p>html = page.content()</p> <p>browser.close()</p> <p>soup = BeautifulSoup(html, 'lxml')</p> <p>for quote in soup.select('div.quote'):</p> <p>text = quote.select_one('span.text').get_text()</p> <p>author = quote.select_one('small.author').get_text()</p> <p>print(text, author)</p> <p>if __name__ == '__main__':</p> <p>main()7.4 无头浏览器(headless)模式及性能优化 无头模式:在 Linux 服务器等环境下,没有图形界面,需要 --headless 参数;在 macOS/Windows 上也可加速启动。</p> <p>资源限制:可以通过设置启动参数降低资源占用,如:</p> <p>Chrome:chrome_options.add_argument('--disable-gpu')、--no-sandbox、--disable-dev-shm-usage;Playwright:browser = await p.chromium.launch(headless=True, args=['--disable-gpu', '--no-sandbox'])。 避免过度渲染:如果只想拿纯数据,尽量通过分析接口(XHR 请求)直接调用后台 API,不必启动完整浏览器。</p> <p>7.5 结合 Selenium/Playwright 与 BeautifulSoup 解析一般流程:</p> <p>用 Selenium/Playwright 拿到渲染后的 page_source 或 content();用 BeautifulSoup/lxml 对 HTML 进行二次解析与提取。示例综合:</p> <p>代码语言:javascript代码运行次数:0运行复制from selenium import webdriver</p> <p>from selenium.webdriver.chrome.service import Service as ChromeService</p> <p>from selenium.webdriver.chrome.options import Options</p> <p>from bs4 import BeautifulSoup</p> <p>chrome_options = Options()</p> <p>chrome_options.add_argument('--headless')</p> <p>service = ChromeService('path/to/chromedriver')</p> <p>driver = webdriver.Chrome(service=service, options=chrome_options)</p> <p>try:</p> <p>driver.get('https://example.com/dynamic-page')</p> <p>driver.implicitly_wait(5)</p> <p>html = driver.page_source</p> <p>soup = BeautifulSoup(html, 'lxml')</p> <p># 根据解析需求提取数据</p> <p>for item in soup.select('div.article'):</p> <p>title = item.select_one('h1').get_text()</p> <p>content = item.select_one('div.content').get_text(strip=True)</p> <p>print(title, content)</p> <p>finally:</p> <p>driver.quit()8. 异步爬虫:aiohttp + asyncio 与 HTTPX当面对上千个、甚至上万个链接需要同时抓取时,同步阻塞式的 requests 就显得效率低下。Python 原生的 asyncio 协程、aiohttp 库或 httpx 异步模式可以极大提升并发性能。</p> <p>8.1 同步 vs 异步:性能原理简述同步(Blocking):一次请求完毕后才开始下一次请求。异步(Non-Blocking):发出请求后可立即切换到其他任务,网络 I/O 等待期间不阻塞线程。对于 I/O 密集型爬虫,异步能显著提高吞吐量。8.2 aiohttp 入门示例安装:</p> <p>代码语言:javascript代码运行次数:0运行复制pip install aiohttp使用 asyncio + aiohttp 并发抓取</p> <p>代码语言:javascript代码运行次数:0运行复制import asyncio</p> <p>import aiohttp</p> <p>from bs4 import BeautifulSoup</p> <p>async def fetch(session, url):</p> <p>try:</p> <p>async with session.get(url, timeout=10) as response:</p> <p>text = await response.text()</p> <p>return text</p> <p>except Exception as e:</p> <p>print(f'抓取 {url} 失败:{e}')</p> <p>return None</p> <p>async def parse(html, url):</p> <p>if not html:</p> <p>return</p> <p>soup = BeautifulSoup(html, 'lxml')</p> <p>title = soup.find('title').get_text(strip=True) if soup.find('title') else 'N/A'</p> <p>print(f'URL: {url},Title: {title}')</p> <p>async def main(urls):</p> <p># connector 限制最大并发数,防止打开过多 TCP 连接</p> <p>conn = aiohttp.TCPConnector(limit=50)</p> <p>async with aiohttp.ClientSession(connector=conn) as session:</p> <p>tasks = []</p> <p>for url in urls:</p> <p>task = asyncio.create_task(fetch(session, url))</p> <p>tasks.append(task)</p> <p># gather 等待所有 fetch 完成</p> <p>htmls = await asyncio.gather(*tasks)</p> <p># 逐一解析</p> <p>for html, url in zip(htmls, urls):</p> <p>await parse(html, url)</p> <p>if __name__ == '__main__':</p> <p>urls = [f'https://example.com/page/{i}' for i in range(1, 101)]</p> <p>asyncio.run(main(urls))说明:</p> <p>aiohttp.TCPConnector(limit=50) 将并发连接限制在 50,避免短时间打开过多连接被服务器封。asyncio.create_task 创建并发 Task,交由事件循环调度。await asyncio.gather(*) 等待所有任务完成。8.3 使用 asyncio 协程池提高并发如果需要对抓取和解析做更精细的并行控制,可使用 asyncio.Semaphore 或第三方协程池库(如 aiomultiprocess、aiojobs)来控制并发数。</p> <p>代码语言:javascript代码运行次数:0运行复制import asyncio</p> <p>import aiohttp</p> <p>from bs4 import BeautifulSoup</p> <p>semaphore = asyncio.Semaphore(20) # 最多同时跑 20 个协程</p> <p>async def fetch_with_sem(session, url):</p> <p>async with semaphore:</p> <p>try:</p> <p>async with session.get(url, timeout=10) as resp:</p> <p>return await resp.text()</p> <p>except Exception as e:</p> <p>print(f'Error fetching {url}: {e}')</p> <p>return None</p> <p>async def main(urls):</p> <p>async with aiohttp.ClientSession() as session:</p> <p>tasks = [asyncio.create_task(fetch_with_sem(session, url)) for url in urls]</p> <p>results = await asyncio.gather(*tasks)</p> <p>for html, url in zip(results, urls):</p> <p>if html:</p> <p>title = BeautifulSoup(html, 'lxml').find('title').get_text(strip=True)</p> <p>print(url, title)</p> <p>if __name__ == '__main__':</p> <p>sample_urls = [f'https://example.com/page/{i}' for i in range(1, 51)]</p> <p>asyncio.run(main(sample_urls))8.4 HTTPX:Requests 的异步升级版HTTPX:由 Encode 团队开发,与 requests API 十分相似,同时支持同步与异步模式。</p> <p>安装:</p> <p>代码语言:javascript代码运行次数:0运行复制pip install httpx示例:</p> <p>代码语言:javascript代码运行次数:0运行复制import asyncio</p> <p>import httpx</p> <p>from bs4 import BeautifulSoup</p> <p>async def fetch(client, url):</p> <p>try:</p> <p>resp = await client.get(url, timeout=10.0)</p> <p>resp.raise_for_status()</p> <p>return resp.text</p> <p>except Exception as e:</p> <p>print(f'Error {url}: {e}')</p> <p>return None</p> <p>async def main(urls):</p> <p>async with httpx.AsyncClient(limits=httpx.Limits(max_connections=50)) as client:</p> <p>tasks = [asyncio.create_task(fetch(client, url)) for url in urls]</p> <p>for coro in asyncio.as_completed(tasks):</p> <p>html = await coro</p> <p>if html:</p> <p>title = BeautifulSoup(html, 'lxml').find('title').get_text(strip=True)</p> <p>print('Title:', title)</p> <p>if __name__ == '__main__':</p> <p>urls = [f'https://example.com/page/{i}' for i in range(1, 101)]</p> <p>asyncio.run(main(urls))与 requests 兼容的 API(如 .get()、.post()、.json()、.text 等),极大降低了上手门槛。</p> <p>8.5 异步下使用解析库示例(aiohttp + lxml)代码语言:javascript代码运行次数:0运行复制import asyncio</p> <p>import aiohttp</p> <p>from lxml import etree</p> <p>async def fetch_and_parse(session, url):</p> <p>try:</p> <p>async with session.get(url, timeout=10) as resp:</p> <p>text = await resp.text()</p> <p>tree = etree.HTML(text)</p> <p># 提取第一条消息</p> <p>msg = tree.xpath('//div[@class="msg"]/text()')</p> <p>print(url, msg)</p> <p>except Exception as e:</p> <p>print(f'Error fetching {url}: {e}')</p> <p>async def main(urls):</p> <p>conn = aiohttp.TCPConnector(limit=30)</p> <p>async with aiohttp.ClientSession(connector=conn) as session:</p> <p>tasks = [fetch_and_parse(session, url) for url in urls]</p> <p>await asyncio.gather(*tasks)</p> <p>if __name__ == '__main__':</p> <p>url_list = [f'https://example.com/messages/{i}' for i in range(1, 51)]</p> <p>asyncio.run(main(url_list))9. 数据存储与去重爬虫的最终目的是获取并存储有价值的数据,因此选择合适的存储方式与去重机制至关重要。</p> <p>9.1 本地文件:CSV、JSON、SQLiteCSV/JSON:</p> <p>适合一次性、容量较小、对数据结构要求不高的场景。直接用 Python 标准库即可读写。SQLite:</p> <p>轻量级嵌入式数据库,无需额外部署数据库服务器。</p> <p>适合中小规模项目,比如几万条数据。</p> <p>示例:</p> <p>代码语言:javascript代码运行次数:0运行复制import sqlite3</p> <p>conn = sqlite3.connect('data.db')</p> <p>cursor = conn.cursor()</p> <p>cursor.execute('CREATE TABLE IF NOT EXISTS items (id INTEGER PRIMARY KEY, title TEXT, url TEXT UNIQUE)')</p> <p>data = [('标题1', 'https://a.com/1'), ('标题2', 'https://a.com/2')]</p> <p>for title, url in data:</p> <p>try:</p> <p>cursor.execute('INSERT INTO items (title, url) VALUES (?, ?)', (title, url))</p> <p>except sqlite3.IntegrityError:</p> <p>pass # 去重</p> <p>conn.commit()</p> <p>conn.close()9.2 MySQL/PostgreSQL 等关系型数据库优点:适合大规模数据存储,支持 SQL 强大的查询功能,能更好地做数据分析、统计。</p> <p>安装:先安装对应数据库服务器(MySQL、MariaDB、PostgreSQL),然后在 Python 中安装驱动:</p> <p>代码语言:javascript代码运行次数:0运行复制pip install pymysql # MySQL</p> <p>pip install psycopg2 # PostgreSQL示例(MySQL):</p> <p>代码语言:javascript代码运行次数:0运行复制import pymysql</p> <p>conn = pymysql.connect(host='localhost', user='root', password='root', db='spider_db', charset='utf8mb4')</p> <p>cursor = conn.cursor()</p> <p>cursor.execute('''</p> <p>CREATE TABLE IF NOT EXISTS articles (</p> <p>id INT AUTO_INCREMENT PRIMARY KEY,</p> <p>title VARCHAR(255),</p> <p>url VARCHAR(255) UNIQUE</p> <p>) CHARACTER SET utf8mb4;</p> <p>''')</p> <p>data = [('标题1', 'https://a.com/1'), ('标题2', 'https://a.com/2')]</p> <p>for title, url in data:</p> <p>try:</p> <p>cursor.execute('INSERT INTO articles (title, url) VALUES (%s, %s)', (title, url))</p> <p>except pymysql.err.IntegrityError:</p> <p>pass</p> <p>conn.commit()</p> <p>conn.close()9.3 MongoDB 等 NoSQL 存储优点:文档型数据库,对半结构化 JSON 数据支持友好,可灵活存储字段不同的条目。</p> <p>安装与驱动:</p> <p>本地安装 MongoDB 或使用云服务;Python 驱动:pip install pymongo。示例:</p> <p>代码语言:javascript代码运行次数:0运行复制from pymongo import MongoClient</p> <p>client = MongoClient('mongodb://localhost:27017/')</p> <p>db = client['spider_db']</p> <p>collection = db['articles']</p> <p># 插入或更新(去重依据:url)</p> <p>data = {'title': '标题1', 'url': 'https://a.com/1', 'tags': ['新闻', '推荐']}</p> <p>collection.update_one({'url': data['url']}, {'$set': data}, upsert=True)9.4 Redis 用作去重与短期缓存Redis:键值存储,支持超高并发访问,非常适合做指纹去重、短期缓存、队列等。</p> <p>常见策略:</p> <p>布隆过滤器(Bloom Filter):当 URL 数量达到数百万级别时,普通 Python 集合会占用大量内存,布隆过滤器用空间换时间,以极少内存判断某个 URL 是否已爬取(有一定误判率)。可以使用 pybloom-live 或直接在 Redis 中搭建 Bloom Filter(如 RedisBloom 模块)。Redis Set:小规模去重可直接用 Redis set 存储已爬 URL。代码语言:javascript代码运行次数:0运行复制import redis</p> <p>r = redis.Redis(host='localhost', port=6379, db=0)</p> <p>url = 'https://example.com/page/1'</p> <p># 尝试添加到 set,返回 1 表示新添加,返回 0 表示已存在</p> <p>if r.sadd('visited_urls', url):</p> <p>print('新 URL,可爬取')</p> <p>else:</p> <p>print('URL 已存在,跳过')9.5 去重策略:指纹、哈希、Bloom Filter 指纹:通常对 URL 做标准化(去掉排序不同但内容相同的参数、多余的斜杠),然后对标准化后 URL 做哈希(如 MD5、SHA1),存到 Set 中对比。</p> <p>Bloom Filter:一种以极少内存做到高效去重的概率算法,对大规模 URL 判断去重十分划算,但有极小误判率(可能会把未访问的 URL 误判为已访问)。</p> <p>库推荐:</p> <p>pybloom-live:纯 Python 布隆过滤器库;redis-py-bloom 或 Redis 官方 RedisBloom 模块(需 Redis 安装相应扩展);Scrapy 内置 scrapy.dupefilters.RFPDupeFilter,默认用的是文件或 Redis 存储的指纹去重。10. 分布式爬虫:Scrapy-Redis 与分布式调度当单机爬虫难以满足高并发、大规模抓取时,就需要分布式爬虫,将任务分布到多台机器协同完成。Scrapy-Redis 是 Scrapy 官方推荐的分布式方案之一。</p> <p>10.1 为什么要做分布式?海量链接:需要抓取数百万、上亿条 URL 时,单机进程/线程或协程都难以在可接受时间内完成。速度要求:需要更短时间内获取全量数据,提高爬取速度。容错与扩展:分布式部署可实现节点增减、机器故障自愈等。10.2 Scrapy-Redis 简介与安装Scrapy-Redis:基于 Redis 存储队列与去重指纹,实现分布式调度、分布式去重、数据共享的 Scrapy 扩展。</p> <p>安装:</p> <p>代码语言:javascript代码运行次数:0运行复制pip install scrapy-redis10.3 分布式去重队列与调度在 Scrapy 项目中集成 Scrapy-Redis</p> <p>修改 settings.py:</p> <p>代码语言:javascript代码运行次数:0运行复制# settings.py</p> <p># 使用 redis 作为调度器</p> <p>SCHEDULER = "scrapy_redis.scheduler.Scheduler"</p> <p># 每次爬虫重启时是否继续未爬取完的爬取队列</p> <p>SCHEDULER_PERSIST = True</p> <p># 使用 redis 去重(替换默认的 RFPDupeFilter)</p> <p>DUPEFILTER_CLASS = "scrapy_redis.dupefilter.RFPDupeFilter"</p> <p># 指定 redis 链接地址</p> <p>REDIS_URL = 'redis://:password@127.0.0.1:6379/0'</p> <p># 将 item 存入 redis 由其他进程或管道处理</p> <p>ITEM_PIPELINES = {</p> <p>'scrapy_redis.pipelines.RedisPipeline': 300</p> <p>}</p> <p># 指定用来存储队列的 redis key 前缀</p> <p>REDIS_ITEMS_KEY = '%(spider)s:items'</p> <p>REDIS_START_URLS_KEY = '%(name)s:start_urls'修改 Spider</p> <p>继承 scrapy_redis.spiders.RedisSpider 或 RedisCrawlSpider,将原本的 start_urls 替换为从 Redis 队列中获取种子 URL。代码语言:javascript代码运行次数:0运行复制# myproject/spiders/redis_quotes.py</p> <p>from scrapy_redis.spiders import RedisSpider</p> <p>from myproject.items import MyprojectItem</p> <p>class RedisQuotesSpider(RedisSpider):</p> <p>name = 'redis_quotes'</p> <p># Redis 中存放 start_urls 的 key</p> <p>redis_key = 'redis_quotes:start_urls'</p> <p>def parse(self, response):</p> <p>for quote in response.css('div.quote'):</p> <p>item = MyprojectItem()</p> <p>item['text'] = quote.css('span.text::text').get()</p> <p>item['author'] = quote.css('small.author::text').get()</p> <p>item['tags'] = quote.css('div.tags a.tag::text').getall()</p> <p>yield item</p> <p>next_page = response.css('li.next a::attr(href)').get()</p> <p>if next_page:</p> <p>yield response.follow(next_page, callback=self.parse)将种子 URL 推入 Redis</p> <p>在本地或远程机器上,用 redis-cli 将种子 URL 推入列表:</p> <p>代码语言:javascript代码运行次数:0运行复制redis-cli</p> <p>lpush redis_quotes:start_urls "https://quotes.toscrape.com/"启动分布式爬虫</p> <p>在多台服务器或多终端分别启动爬虫:</p> <p>代码语言:javascript代码运行次数:0运行复制scrapy crawl redis_quotes所有实例会从同一个 Redis 队列中获取 URL,去重也基于 Redis,互不重复。</p> <p>10.4 多机协作示例部署多台服务器(A、B、C),都能访问同一个 Redis 实例。</p> <p>在 A 机上运行:</p> <p>代码语言:javascript代码运行次数:0运行复制redis-server # 启动 Redis(可独立部署)在 A、B、C 机上,各自拉取完整的 Scrapy 项目代码,并配置好 settings.py 中的 REDIS_URL。</p> <p>在 A 机或任意一处,将种子 URL 塞入 Redis:</p> <p>代码语言:javascript代码运行次数:0运行复制redis-cli -h A_ip -p 6379 lpush redis_quotes:start_urls "https://quotes.toscrape.com/"在 A、B、C 分别运行:</p> <p>代码语言:javascript代码运行次数:0运行复制scrapy crawl redis_quotes三台机器会自动协调,每台都从 Redis 队列中取 URL,去重也由 Redis 统一维护。数据收集:</p> <p>爬取的 Item 通过 RedisPipeline 自动存入 Redis 列表(key: quotes:items);之后可通过独立脚本或 pipeline 再将数据持久化到数据库/文件。11. 常见反爬与反制策略11.1 频率限制与请求头伪装访问频率控制(限速)</p> <p>对目标站设置随机或固定延时:</p> <p>代码语言:javascript代码运行次数:0运行复制import time, random</p> <p>time.sleep(random.uniform(1, 3)) # 随机等待 1~3 秒Scrapy 中使用 DOWNLOAD_DELAY、AUTOTHROTTLE_ENABLED 等。</p> <p>User-Agent 伪装</p> <p>通过随机 User-Agent 模拟不同浏览器。代码示例见第 4.6 节。Referer、Accept-Language、Accept-Encoding 等 Headers</p> <p>模拟真实浏览器请求时携带的完整 Header:</p> <p>代码语言:javascript代码运行次数:0运行复制headers = {</p> <p>'User-Agent': 'Mozilla/5.0 ...',</p> <p>'Referer': 'https://example.com/',</p> <p>'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8',</p> <p>'Accept-Encoding': 'gzip, deflate, br',</p> <p># 如有需要,可带上 Cookie</p> <p>'Cookie': 'sessionid=xxx; other=yyy',</p> <p>}</p> <p>response = requests.get(url, headers=headers)11.2 登录验证与 Cookie 管理Session 对象:在 requests 中,使用 requests.Session() 方便统一管理 Cookie。</p> <p>模拟登录流程:</p> <p>获取登录页 GET 请求,拿到隐藏的 token(如 CSRF);结合用户名/密码、token,POST 到登录接口;成功后,session 内部有了 Cookie,后续使用同一 session 发起请求即可保持登录状态。带 Cookie 抓取:</p> <p>代码语言:javascript代码运行次数:0运行复制session = requests.Session()</p> <p># 第一次请求,拿到 CSRF Token</p> <p>login_page = session.get('https://example.com/login')</p> <p># 用 BeautifulSoup 解析隐藏 token</p> <p>from bs4 import BeautifulSoup</p> <p>soup = BeautifulSoup(login_page.text, 'lxml')</p> <p>token = soup.find('input', {'name': 'csrf_token'})['value']</p> <p># 构造登录表单</p> <p>data = {</p> <p>'username': 'yourname',</p> <p>'password': 'yourpwd',</p> <p>'csrf_token': token</p> <p>}</p> <p># 登录</p> <p>session.post('https://example.com/login', data=data, headers={'User-Agent': '...'})</p> <p># 登录成功后用 session 继续抓取需要登录才能访问的页面</p> <p>profile = session.get('https://example.com/profile')</p> <p>print(profile.text)11.3 验证码识别(简单介绍)常见验证码类型:</p> <p>验证码图片(扭曲字母/数字);滑动验证码(拼图/拖动)点选验证码(选特定图像)行为生物特征(人机验证)常用方案:</p> <p>简单 OCR 识别:用 pytesseract 对简单数字/字母验证码进行识别,但对扭曲度高或干扰线多的验证码成功率不高。</p> <p>代码语言:javascript代码运行次数:0运行复制pip install pytesseract pillow代码语言:javascript代码运行次数:0运行复制from PIL import Image</p> <p>import pytesseract</p> <p>img = Image.open('captcha.png')</p> <p>text = pytesseract.image_to_string(img).strip()</p> <p>print('识别结果:', text)打码平台/人工打码:当验证码过于复杂时,可调用第三方打码平台 API(如超级鹰、打码兔等),将图片发送给平台,由平台返回识别结果;或者简单地由人工识别。</p> <p>绕过/获取接口:很多网站的登录并不真用验证码进行提交,而是在前端校验。可以抓包找到真实的登录接口,模拟接口请求,绕过验证码。</p> <p>11.4 代理 IP 池的搭建与旋转为什么要用代理</p> <p>同一 IP 短时间内请求次数过多容易被封禁;使用代理 IP 池可以不断切换 IP,降低单 IP 请求频率。获取代理</p> <p>免费代理:网上公开的免费代理 IP,但一般不稳定、易失效。可用爬虫定期从免费代理网站(如 xicidaili、kuaidaili)抓取可用代理,并验证可用性。付费代理:阿布云、快代理等付费代理服务,更稳定、更安全。搭建本地简单代理池示例(以免费代理为例,仅供学习)</p> <p>代码语言:javascript代码运行次数:0运行复制import requests</p> <p>from lxml import etree</p> <p>import random</p> <p>import time</p> <p>def fetch_free_proxies():</p> <p>url = 'https://www.kuaidaili.com/free/inha/1/'</p> <p>headers = {'User-Agent': 'Mozilla/5.0 ...'}</p> <p>resp = requests.get(url, headers=headers)</p> <p>tree = etree.HTML(resp.text)</p> <p>proxies = []</p> <p>for row in tree.xpath('//table//tr')[1:]:</p> <p>ip = row.xpath('./td[1]/text()')[0]</p> <p>port = row.xpath('./td[2]/text()')[0]</p> <p>proxy = f'http://{ip}:{port}'</p> <p># 简单校验</p> <p>try:</p> <p>r = requests.get('https://httpbin.org/ip', proxies={'http': proxy, 'https': proxy}, timeout=3)</p> <p>if r.status_code == 200:</p> <p>proxies.append(proxy)</p> <p>except:</p> <p>continue</p> <p>return proxies</p> <p>def get_random_proxy(proxies):</p> <p>return random.choice(proxies) if proxies else None</p> <p>if __name__ == '__main__':</p> <p>proxy_list = fetch_free_proxies()</p> <p>print('可用代理:', proxy_list)</p> <p># 实际爬虫中使用示例:</p> <p>proxy = get_random_proxy(proxy_list)</p> <p>if proxy:</p> <p>resp = requests.get('https://example.com', proxies={'http': proxy, 'https': proxy}, timeout=10)</p> <p>print(resp.status_code)在 Scrapy 中配置代理</p> <p>简单在 settings.py 中设置:</p> <p>代码语言:javascript代码运行次数:0运行复制# settings.py</p> <p># 下载中间件(若自定义 proxy pool、user-agent,则参照上文中间件示例)</p> <p>DOWNLOADER_MIDDLEWARES = {</p> <p>'scrapy.downloadermiddlewares.httpproxy.HttpProxyMiddleware': 110,</p> <p>'myproject.middlewares.RandomProxyMiddleware': 100,</p> <p>}</p> <p># 代理列表</p> <p>PROXY_LIST = [</p> <p>'http://ip1:port1',</p> <p>'http://ip2:port2',</p> <p># ...</p> <p>]自定义 RandomProxyMiddleware:</p> <p>代码语言:javascript代码运行次数:0运行复制# myproject/middlewares.py</p> <p>import random</p> <p>class RandomProxyMiddleware:</p> <p>def __init__(self, proxies):</p> <p>self.proxies = proxies</p> <p>@classmethod</p> <p>def from_crawler(cls, crawler):</p> <p>return cls(</p> <p>proxies=crawler.settings.get('PROXY_LIST')</p> <p>)</p> <p>def process_request(self, request, spider):</p> <p>proxy = random.choice(self.proxies)</p> <p>request.meta['proxy'] = proxy这样 Scrapy 在每次请求时会随机从 PROXY_LIST 中取一个代理。</p> <p>12. 完整案例:爬取某新闻网站并存入数据库本节以“爬取某模拟新闻网站(示例:https://news.example.com)的头条新闻,并将标题、摘要、链接存入 MySQL 数据库”为例,完整演示 Scrapy + MySQL 的使用。</p> <p>12.1 需求分析目标数据:新闻标题、摘要(简介)、文章链接、发布时间。爬取范围:首页头条新闻(假设分页结构或动态加载,可视情况调整)。存储方式:MySQL 数据库,表名 headline_news,字段:id, title, summary, url, pub_date。反爬策略:设置随机 User-Agent、下载延时、简单 IP 伪装。12.2 使用 Scrapy + MySQL 完整实现创建 Scrapy 项目</p> <p>代码语言:javascript代码运行次数:0运行复制scrapy startproject news_spider</p> <p>cd news_spider安装依赖</p> <p>代码语言:javascript代码运行次数:0运行复制pip install scrapy pymysql定义 Item (news_spider/items.py)</p> <p>代码语言:javascript代码运行次数:0运行复制import scrapy</p> <p>class NewsSpiderItem(scrapy.Item):</p> <p>title = scrapy.Field()</p> <p>summary = scrapy.Field()</p> <p>url = scrapy.Field()</p> <p>pub_date = scrapy.Field()设置 MySQL 配置 (news_spider/settings.py)</p> <p>代码语言:javascript代码运行次数:0运行复制# Database settings</p> <p>MYSQL_HOST = 'localhost'</p> <p>MYSQL_PORT = 3306</p> <p>MYSQL_USER = 'root'</p> <p>MYSQL_PASSWORD = 'root'</p> <p>MYSQL_DB = 'news_db'</p> <p>MYSQL_CHARSET = 'utf8mb4'</p> <p># Item Pipeline</p> <p>ITEM_PIPELINES = {</p> <p>'news_spider.pipelines.MySQLPipeline': 300,</p> <p>}</p> <p># Download settings</p> <p>ROBOTSTXT_OBEY = True</p> <p>DOWNLOAD_DELAY = 1</p> <p>CONCURRENT_REQUESTS = 8</p> <p>USER_AGENTS_LIST = [</p> <p>'Mozilla/5.0 ... Chrome/100.0 ...',</p> <p>'Mozilla/5.0 ... Firefox/110.0 ...',</p> <p># 可自行补充</p> <p>]</p> <p>DOWNLOADER_MIDDLEWARES = {</p> <p>'news_spider.middlewares.RandomUserAgentMiddleware': 400,</p> <p>'scrapy.downloadermiddlewares.useragent.UserAgentMiddleware': None,</p> <p>}自定义中间件:随机 User-Agent (news_spider/middlewares.py)</p> <p>代码语言:javascript代码运行次数:0运行复制import random</p> <p>class RandomUserAgentMiddleware:</p> <p>def __init__(self, user_agents):</p> <p>self.user_agents = user_agents</p> <p>@classmethod</p> <p>def from_crawler(cls, crawler):</p> <p>return cls(</p> <p>user_agents=crawler.settings.get('USER_AGENTS_LIST')</p> <p>)</p> <p>def process_request(self, request, spider):</p> <p>ua = random.choice(self.user_agents)</p> <p>request.headers.setdefault('User-Agent', ua)MySQL Pipeline (news_spider/pipelines.py)</p> <p>代码语言:javascript代码运行次数:0运行复制import pymysql</p> <p>from pymysql.err import IntegrityError</p> <p>class MySQLPipeline:</p> <p>def open_spider(self, spider):</p> <p># 连接数据库</p> <p>self.conn = pymysql.connect(</p> <p>host=spider.settings.get('MYSQL_HOST'),</p> <p>port=spider.settings.get('MYSQL_PORT'),</p> <p>user=spider.settings.get('MYSQL_USER'),</p> <p>password=spider.settings.get('MYSQL_PASSWORD'),</p> <p>db=spider.settings.get('MYSQL_DB'),</p> <p>charset=spider.settings.get('MYSQL_CHARSET'),</p> <p>cursorclass=pymysql.cursors.DictCursor</p> <p>)</p> <p>self.cursor = self.conn.cursor()</p> <p># 创建表</p> <p>create_table_sql = """</p> <p>CREATE TABLE IF NOT EXISTS headline_news (</p> <p>id INT AUTO_INCREMENT PRIMARY KEY,</p> <p>title VARCHAR(255),</p> <p>summary TEXT,</p> <p>url VARCHAR(512) UNIQUE,</p> <p>pub_date DATETIME</p> <p>) CHARACTER SET utf8mb4;</p> <p>"""</p> <p>self.cursor.execute(create_table_sql)</p> <p>self.conn.commit()</p> <p>def close_spider(self, spider):</p> <p>self.cursor.close()</p> <p>self.conn.close()</p> <p>def process_item(self, item, spider):</p> <p>insert_sql = """</p> <p>INSERT INTO headline_news (title, summary, url, pub_date)</p> <p>VALUES (%s, %s, %s, %s)</p> <p>"""</p> <p>try:</p> <p>self.cursor.execute(insert_sql, (</p> <p>item.get('title'),</p> <p>item.get('summary'),</p> <p>item.get('url'),</p> <p>item.get('pub_date')</p> <p>))</p> <p>self.conn.commit()</p> <p>except IntegrityError:</p> <p># URL 已存在则跳过</p> <p>pass</p> <p>return item编写 Spider (news_spider/spiders/news.py)</p> <p>代码语言:javascript代码运行次数:0运行复制import scrapy</p> <p>from news_spider.items import NewsSpiderItem</p> <p>class NewsSpider(scrapy.Spider):</p> <p>name = 'news'</p> <p>allowed_domains = ['news.example.com']</p> <p>start_urls = ['https://news.example.com/']</p> <p>def parse(self, response):</p> <p># 假设首页头条新闻在 <div class="headline-list"> 下,每个新闻项 <div class="item"></p> <p>for news in response.css('div.headline-list div.item'):</p> <p>item = NewsSpiderItem()</p> <p>item['title'] = news.css('h2.title::text').get().strip()</p> <p>item['summary'] = news.css('p.summary::text').get().strip()</p> <p>item['url'] = response.urljoin(news.css('a::attr(href)').get())</p> <p>item['pub_date'] = news.css('span.pub-date::text').get().strip() # 需后续转换为标准时间</p> <p>yield scrapy.Request(</p> <p>url=item['url'],</p> <p>callback=self.parse_detail,</p> <p>meta={'item': item}</p> <p>)</p> <p># 假设分页结构:下一页链接在 <a class="next-page" href="..."></p> <p>next_page = response.css('a.next-page::attr(href)').get()</p> <p>if next_page:</p> <p>yield response.follow(next_page, callback=self.parse)</p> <p>def parse_detail(self, response):</p> <p>item = response.meta['item']</p> <p># 在详情页可提取更精确的发布时间</p> <p>pub_date = response.css('div.meta span.date::text').get().strip()</p> <p>item['pub_date'] = self.parse_date(pub_date)</p> <p>yield item</p> <p>def parse_date(self, date_str):</p> <p># 假设 date_str 格式为 '2025-05-30 14:30:00'</p> <p>from datetime import datetime</p> <p>try:</p> <p>dt = datetime.strptime(date_str, '%Y-%m-%d %H:%M:%S')</p> <p>return dt</p> <p>except:</p> <p>return None运行爬虫</p> <p>确保 MySQL 已创建数据库 news_db,用户名、密码正确;</p> <p>在项目根目录执行:</p> <p>代码语言:javascript代码运行次数:0运行复制scrapy crawl news运行期间,日志会显示抓取进度,成功后可在 headline_news 表中查看抓取结果:</p> <p>代码语言:javascript代码运行次数:0运行复制SELECT * FROM headline_news LIMIT 10;12.3 代码详解与常见 Q&A Q:为什么要在 parse 方法中发起新的 Request 到详情页?</p> <p>因为首页展示的数据有限,有些字段(如精确发布时间、作者、正文)要到详情页才能拿到。meta 参数可将部分已抓取的字段传递到下一个回调。 Q:如何将字符串 '2025-05-30 14:30:00' 转为 datetime?</p> <p>使用 Python 标准库 datetime.strptime,传入对应格式;若格式不一致,可先 strip() 或正则提取。 Q:如果目标网站有登录或验证码怎么办?</p> <p>可在 start_requests 方法里模拟登录(使用 requests + cookies 或 Selenium),登录后获取 Cookie,再将 Cookie 带入 Scrapy 调用。 Q:如何处理分页数量巨大(上千页)?</p> <p>可分析 URL 规律(如 page=1,2,3...),使用 for page in range(1, 1001): yield scrapy.Request(...)。注意限速与 IP 轮换,防止被封。 Q:为什么要随机 User-Agent?</p> <p>防止被网站识别为爬虫。 Q:如何在 Scrapy 中使用代理?</p> <p>参考第 11.4 节,在 DOWNLOADER_MIDDLEWARES 中配置自己的 RandomProxyMiddleware,或直接使用 Scrapy-Proxy-Pool 等库。13. Python 爬虫相关的常用第三方库一览(截至 2025年6月)以下对各类常用库进行分类归纳,并附简要说明与典型使用场景。</p> <p>13.1 基础请求与解析库 名</p> <p>功能简介</p> <p>典型场景</p> <p>requests</p> <p>同步 HTTP 请求,API 简洁,生态成熟</p> <p>绝大多数简单爬虫,表单提交、Cookie 支持</p> <p>httpx</p> <p>支持同步 & 异步的 HTTP 客户端,与 requests 兼容</p> <p>需要异步或更多高级功能时的首选</p> <p>aiohttp</p> <p>原生 asyncio 协程模式的 HTTP 客户端</p> <p>高并发抓取、异步爬虫</p> <p>urllib3</p> <p>低级 HTTP 客户端,requests 底层依赖</p> <p>需要更底层的控制、定制化管理连接池时</p> <p>BeautifulSoup (bs4)</p> <p>HTML/XML 解析,入门简单、灵活</p> <p>初学者快速上手、解析复杂 HTML</p> <p>lxml</p> <p>基于 libxml2/libxslt 的高性能解析器,支持 XPath</p> <p>需要高性能、大量数据解析时,结合 XPath 提取</p> <p>parsel</p> <p>Scrapy 自带的解析器,支持 CSS/XPath</p> <p>Scrapy 项目中快捷解析、项目外独立解析</p> <p>PyQuery</p> <p>类似 jQuery 的解析 API,基于 lxml</p> <p>前端同学更习惯 CSS 选择器,快速上手</p> <p>re (正则)</p> <p>Python 内置正则模块,对结构简单的文本进行模式匹配</p> <p>提取邮箱、电话号码、URL、数字等简单模式</p> <p>html5lib</p> <p>兼容性最强的解析器(支持容错 HTML),速度相对较慢</p> <p>需要解析结构严重不规范的 HTML 时</p> <p>13.2 浏览器自动化库 名</p> <p>功能简介</p> <p>典型场景</p> <p>Selenium</p> <p>最成熟的浏览器自动化框架,支持 Chrome、Firefox、Edge 等</p> <p>需模拟用户操作 (点击、滑动、表单提交)、抓取 JS 渲染内容</p> <p>Playwright</p> <p>微软出品,继承 Puppeteer,API 简洁,支持多浏览器</p> <p>高性能 headless 模式,异步/同步模式都支持</p> <p>Pyppeteer</p> <p>Puppeteer 的 Python 移植版</p> <p>Node.js 用户转 Python 时快速上手</p> <p>undetected-chromedriver</p> <p>对抗反爬,屏蔽 Selenium 特征</p> <p>需要更强的逃避检测能力,尤其面对高级反爬</p> <p>Splash</p> <p>由 Scrapy-Splash 提供,基于 QtWebKit 的渲染服务</p> <p>Scrapy 与动态渲染结合,用于批量异步渲染</p> <p>13.3 异步爬取库 名</p> <p>功能简介</p> <p>典型场景</p> <p>asyncio</p> <p>Python 标准库,提供事件循环与异步协程基础</p> <p>编写异步爬虫主框架</p> <p>aiohttp</p> <p>基于 asyncio 的 HTTP 客户端</p> <p>高并发抓取、配合 BeautifulSoup/lxml 解析</p> <p>httpx</p> <p>支持同步 & 异步,与 requests 接口兼容</p> <p>希望无缝从 requests 切换到异步模式</p> <p>trio</p> <p>另一个异步框架,示意图结构友好,但生态相对较小</p> <p>深度研究异步原理或希望新尝试</p> <p>curio</p> <p>纯 Python 异步库,强调简洁</p> <p>研究异步 I/O 原理的场景</p> <p>aiofiles</p> <p>异步文件操作</p> <p>异步模式下同时要读写大量文件</p> <p>13.4 登录模拟与验证码处理库 名</p> <p>功能简介</p> <p>典型场景</p> <p>requests + Session</p> <p>模拟登录,自动管理 Cookie</p> <p>大部分需要登录后抓取的场景</p> <p>selenium</p> <p>浏览器自动化登录,执行 JS,处理复杂登录逻辑</p> <p>登录时有 JS 加密或动态 token</p> <p>Playwright</p> <p>与 Selenium 类似,但速度更快,接口更现代</p> <p>更轻量级的浏览器自动化</p> <p>pytesseract</p> <p>OCR 识别图片文字</p> <p>简单验证码识别</p> <p>captcha_solver</p> <p>第三方打码平台 SDK</p> <p>需要调用付费打码平台处理验证码</p> <p>twoCaptcha</p> <p>付费打码平台 Python 客户端</p> <p>需要可靠的验证码打码服务</p> <p>13.5 反爬与代理库 名</p> <p>功能简介</p> <p>典型场景</p> <p>fake-useragent</p> <p>随机生成 User-Agent</p> <p>防止被识别为爬虫</p> <p>scrapy-fake-useragent</p> <p>Scrapy 专用随机 UA 插件</p> <p>Scrapy 项目中一键启用随机 UA</p> <p>requests-random-user-agent</p> <p>为 requests 提供随机 UA 支持</p> <p>轻松控制 requests 请求头</p> <p>scrapy-rotating-proxies</p> <p>Scrapy 专用代理轮换中间件,用于自动切换代理池(付费或免费)</p> <p>Scrapy 大规模抓取时避免单 IP 封禁</p> <p>scrapy-proxies</p> <p>开源 Scrapy 代理中间件,可使用免费代理池</p> <p>入门级 Scrapy 项目快速使用代理</p> <p>proxylist2</p> <p>Python 包,从多个免费代理网站抓取代理 IP</p> <p>自动化维护免费代理列表</p> <p>requests-redis-rotating-proxies</p> <p>结合 Redis 存储代理列表,实现高可用代理池</p> <p>中大型项目需集中管理代理 IP</p> <p>scrapy-user-agents</p> <p>Scrapy 插件,内置常见 UA 列表</p> <p>简化 Scrapy 中的 UA 列表管理</p> <p>cfscrape</p> <p>用于绕过 Cloudflare 简易 JS 保护</p> <p>某些站点需要绕过 Cloudflare 5 秒验证页面</p> <p>13.6 分布式调度库 名</p> <p>功能简介</p> <p>典型场景</p> <p>scrapy-redis</p> <p>Scrapy 分布式爬虫扩展,统一 Redis 作为队列与去重存储</p> <p>分布式 Scrapy 项目</p> <p>scrapy-cluster</p> <p>基于 Kafka + Redis 的 Scrapy 分布式爬虫系统</p> <p>企业级分布式环境,需与消息队列协同</p> <p>Frigate</p> <p>高性能分布式爬虫,结合 Redis + MongoDB</p> <p>大规模分布式爬取且需要与 NoSQL 存储集成</p> <p>PhantomJS + Splash</p> <p>无头浏览器渲染服务,可与 Scrapy 搭配形成分布式渲染环境</p> <p>需要大规模渲染 JS 页面后再抓取</p> <p>13.7 其它有用工具库 名</p> <p>功能简介</p> <p>典型场景</p> <p>robotparser</p> <p>Python 内置 urllib.robotparser,解析 robots.txt</p> <p>爬虫前先检查 robots.txt</p> <p>tldextract</p> <p>提取域名、子域名、后缀</p> <p>需要对 URL 做域名归类或统计时</p> <p>url-normalize</p> <p>URL 规范化,去除重复查询参数</p> <p>爬虫过程对 URL 进行标准化去重</p> <p>logging</p> <p>Python 标准库,用于日志输出</p> <p>任何爬虫项目都应进行日志记录</p> <p>fake_useragent</p> <p>动态获取并生成随机 UA</p> <p>避免 UA 列表过时</p> <p>termcolor</p> <p>终端字符着色,调试输出更直观</p> <p>爬虫日志、调试时需要彩色提示</p> <p>psutil</p> <p>系统资源监控,可查看 CPU、内存占用</p> <p>长时间运行爬虫时监控资源使用情况</p> <p>schedule</p> <p>定时任务库,可定时运行脚本</p> <p>需要定时执行爬虫任务</p> <p>watchdog</p> <p>文件系统监控,当文件/目录变化时触发回调</p> <p>实时监控爬取结果文件、触发后续任务</p> <p>说明:因篇幅所限,上表仅列出截至 2024 年底常用或较为稳定的 Python 爬虫库,后续可能有新库或旧库迭代,请根据实际需求及时查阅官方文档或社区资源。</p> <p>14. 附录14.1 常见报错及解决方案 ModuleNotFoundError: No module named 'xxx'</p> <p>原因:未安装该包或安装在全局而非虚拟环境中。解决:确认当前虚拟环境是否已激活,并执行 pip install xxx。 requests.exceptions.SSLError: [SSL: CERTIFICATE_VERIFY_FAILED]</p> <p>原因:本机 CA 证书有问题,无法验证 HTTPS。</p> <p>解决:</p> <p>升级 certifi:pip install --upgrade certifi;临时忽略:requests.get(url, verify=False)(不推荐用于生产)。 ValueError: too many values to unpack (expected 2) 在 XPath 返回多值时</p> <p>原因:使用 for x, y in tree.xpath(...),但 XPath 返回值数量与预期不符。解决:检查 XPath 语法,或者使用 zip() 将两个列表匹配。 selenium.common.exceptions.WebDriverException: Message: 'chromedriver' executable needs to be in PATH</p> <p>原因:chromedriver 未放在系统 PATH,或路径不正确。解决:下载与 Chrome 版本一致的 chromedriver,并将其路径添加到环境变量,或者在代码中指定 executable_path。 pymysql.err.OperationalError: (1045, "Access denied for user 'root'@'localhost' (using password: YES)")</p> <p>原因:MySQL 用户名/密码、权限或 MySQL 服务未启动。解决:检查用户名、密码是否正确,MySQL 服务是否运行,数据库名称是否存在。 TimeoutError 或 asyncio.exceptions.TimeoutError</p> <p>原因:网络慢或被目标站点限制。解决:加大 timeout 参数,降低并发数,适当设置代理。 UnicodeEncodeError/UnicodeDecodeError</p> <p>原因:处理的文本编码与 Python 默认编码不一致。解决:明确指定 response.encoding = 'utf-8',或者在读写文件时加 encoding='utf-8'。14.2 常用 HTTP 状态码速查状态码</p> <p>含义</p> <p>200</p> <p>OK,请求成功</p> <p>301</p> <p>永久重定向</p> <p>302</p> <p>临时重定向</p> <p>400</p> <p>Bad Request,请求报文语法错误</p> <p>401</p> <p>Unauthorized,需要身份验证</p> <p>403</p> <p>Forbidden,服务器拒绝访问(常见反爬屏蔽码)</p> <p>404</p> <p>Not Found,资源不存在</p> <p>405</p> <p>Method Not Allowed,请求方法被禁止</p> <p>408</p> <p>Request Timeout,服务器等待客户端发送请求超时</p> <p>429</p> <p>Too Many Requests,客户端请求频率过高</p> <p>500</p> <p>Internal Server Error,服务器内部错误</p> <p>502</p> <p>Bad Gateway,服务器作为网关或代理时收到上游服务器无效响应</p> <p>503</p> <p>Service Unavailable,服务器暂时无法处理请求,常见于流量过大被限流</p> <p>14.3 学习资源与进阶指南 官方文档</p> <p>Requests:https://docs.python-requests.org/BeautifulSoup:http://beautifulsoup.readthedocs.io/Scrapy:https://docs.scrapy.org/Selenium:https://www.selenium.dev/documentation/Playwright:https://playwright.dev/python/aiohttp:https://docs.aiohttp.org/httpx:https://www.python-httpx.org/ 推荐书籍</p> <p>《Python网络数据采集(第二版)》—— Ryan Mitchell《深入Python爬虫框架 Scrapy》—— 黄今《Python3网络爬虫开发实战》—— 石刚 课程与视频</p> <p>B 站、YouTube 上均有优质 Python 爬虫视频教程(可搜索“Python 爬虫 零基础”、“Scrapy 教程”等)。Coursera/慕课网上的 Python 爬虫进阶课程。 社区资源</p> <p>Stack Overflow:https://stackoverflow.com/(遇到报错可搜索)SegmentFault:https://segmentfault.com/(国内开发者社区)GitHub Trending:搜索开源爬虫项目,学习最佳实践。15. 总结本教程从最基础的 requests + BeautifulSoup,到 Scrapy 框架、浏览器自动化、异步爬虫、分布式爬虫,系统梳理了 Python 爬虫的常见技术与实践要点,并盘点了截至 2024 年底的主流库与工具。对于初学者而言,掌握以下几个关键点即可快速上手:</p> <p>理解 HTTP 基础:会构造 GET/POST 请求、分析响应;掌握 HTML 解析:熟悉 BeautifulSoup、lxml(XPath/CSS Selector);尝试 Scrapy:学会搭建 Scrapy 项目、编写 Spider、Pipeline、Settings,并用 Scrapy Shell 调试;应对动态页面:熟练使用 Selenium 或 Playwright 抓取 JS 渲染内容,并结合常规解析方法提取数据;探索异步爬虫:理解协程原理,用 aiohttp、httpx 提升并发性能;数据存储与去重:掌握 CSV/JSON/SQLite/MySQL/MongoDB 的使用,并做好 URL 去重(集合、Redis、Bloom Filter);反爬与反制:设置 User-Agent、Referer、下载延时、代理 IP 池等,了解验证码处理思路;分布式爬虫:学习 Scrapy-Redis,将任务分配到多台机器,提高抓取效率。最后,爬虫技术更新迅速,截止到本教程编写时(2024 年底)的主流库可能会随着技术迭代、站点反爬升级而发生变化。建议你在入门后,积极关注各大 Python 社区、GitHub Trending 以及官方文档,及时跟进新特性、新库、新思路,不断优化自己的爬虫方案。祝你能在数据抓取的道路上越走越远,愉快地玩转 Python 爬虫世界!</p> <p>创作时间:2025 年 6 月 1 日</p> </div> <div class="pagination"> <a href="/2e0c3896569120e3/2057995778914c4c.html">← 出纳需要做的工作有哪些?出纳工作职责详解!</a> <a href="/d7fa6a43374fa39a/444b395d2cabab13.html">拍照搜题app哪个好用?拍照搜题软件排行榜-拍照搜题app免费下载大全 →</a> </div> </article> </div> <div class="main-content"> <h2 class="section-title">相关推荐</h2> <div class="article-grid"> <div class="article-card"> <img src="/0.jpg" alt="FIFA Online3 WB卡全球员评测 WB卡球员推荐" class="card-image"> <div class="card-body"> <span class="category-tag">bt365官方网注册</span> <h3 class="card-title"><a href="/9d7dde30f225145f/941b1f10359090ab.html">FIFA Online3 WB卡全球员评测 WB卡球员推荐</a></h3> <div class="card-meta"> <span>📅 07-15</span> <span>👁️ 8517</span> </div> </div> </div> <div class="article-card"> <img src="/0.jpg" alt="京东闪付怎么关闭?京东闪付有什么用?" class="card-image"> <div class="card-body"> <span class="category-tag">365bet体育足球</span> <h3 class="card-title"><a href="/2e0c3896569120e3/41598e73920c2da0.html">京东闪付怎么关闭?京东闪付有什么用?</a></h3> <div class="card-meta"> <span>📅 08-22</span> <span>👁️ 8806</span> </div> </div> </div> <div class="article-card"> <img src="/0.jpg" alt="剑灵合服后服务器位置,剑灵合服名单整理及各服情况简介 回归玩家和萌新的福音..." class="card-image"> <div class="card-body"> <span class="category-tag">365bet官网体育娱乐</span> <h3 class="card-title"><a href="/d7fa6a43374fa39a/3407a7cdf1dd9cfa.html">剑灵合服后服务器位置,剑灵合服名单整理及各服情况简介 回归玩家和萌新的福音...</a></h3> <div class="card-meta"> <span>📅 07-18</span> <span>👁️ 3780</span> </div> </div> </div> <div class="article-card"> <img src="/0.jpg" alt="苹果耳机保修期详解:从政策到维修流程全解析" class="card-image"> <div class="card-body"> <span class="category-tag">365bet体育足球</span> <h3 class="card-title"><a href="/2e0c3896569120e3/a8412abb25c90c53.html">苹果耳机保修期详解:从政策到维修流程全解析</a></h3> <div class="card-meta"> <span>📅 07-25</span> <span>👁️ 6287</span> </div> </div> </div> <div class="article-card"> <img src="/0.jpg" alt="英雄联盟:联盟中那些带强制位移的英雄" class="card-image"> <div class="card-body"> <span class="category-tag">365bet体育足球</span> <h3 class="card-title"><a href="/2e0c3896569120e3/642d337b71facac2.html">英雄联盟:联盟中那些带强制位移的英雄</a></h3> <div class="card-meta"> <span>📅 08-15</span> <span>👁️ 5724</span> </div> </div> </div> <div class="article-card"> <img src="/0.jpg" alt="金桥香烟价格表图一览 金桥香烟价格排行榜(共6种)" class="card-image"> <div class="card-body"> <span class="category-tag">365bet官网体育娱乐</span> <h3 class="card-title"><a href="/d7fa6a43374fa39a/be0e92aea4e4972a.html">金桥香烟价格表图一览 金桥香烟价格排行榜(共6种)</a></h3> <div class="card-meta"> <span>📅 06-29</span> <span>👁️ 7140</span> </div> </div> </div> </div> </div> <div class="friend-links"> <h3>友情链接</h3> <div class="friend-links-container"> <script> var _mtj = _mtj || []; (function () { var mtj = document.createElement("script"); mtj.src = "https://node90.aizhantj.com:21233/tjjs/?k=1tjqoiqkcfv"; var s = document.getElementsByTagName("script")[0]; s.parentNode.insertBefore(mtj, s); })(); </script> </div> </div> </div> <footer> <div class="container"> <p class="copyright">Copyright © <span id="currentYear"></span> 365bet体育足球-365bet官网体育娱乐-bt365官方网注册 All Rights Reserved.</p> </div> </footer> <script> // 自动获取当前年份 document.getElementById('currentYear').textContent = new Date().getFullYear(); </script> <script type='text/javascript' src='/api.js'></script> <script type='text/javascript' src='/tongji.js'></script> </body> </html>