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

FIFA Online3 WB卡全球员评测 WB卡球员推荐

京东闪付怎么关闭?京东闪付有什么用?

剑灵合服后服务器位置,剑灵合服名单整理及各服情况简介 回归玩家和萌新的福音...

苹果耳机保修期详解:从政策到维修流程全解析

英雄联盟:联盟中那些带强制位移的英雄
