用 Python 写你的第一个爬虫:小白也能轻松搞定数据抓取(超详细包含最新所有Python爬虫库的教程)
本文是一篇面向爬虫爱好者的超详细 Python 爬虫入门教程,涵盖了从基础到进阶的所有关键技术点:使用 Requests 与 BeautifulSoup 实现静态网页数据抓取,运用 lxml、XPath、CSS 选择器等高效解析技术,深入 Scrapy 框架搭建分布式爬虫项目,掌握 Selenium 和 Playwright 浏览器自动化处理 JS 动态渲染,探索 aiohttp、HTTPX 异步爬虫提升并发性能,并结合代理 IP 池、User-Agent 伪装、验证码识别等反爬虫策略应对电商数据抓取、新闻数据爬取、社交媒体采集等场景。快速上手大规模爬虫项目,打造可扩展、高效稳定的数据抓取解决方案。
在信息爆炸的时代,互联网早已成为最丰富、最便捷的数据来源。从电商平台的商品价格到新闻网站的最新动态,从社交媒体的热门话题到招聘网站的职位信息,只要你想得到,几乎都能通过爬虫从网页里“扒”出来。对于初学者而言,爬虫其实并不神秘:只要理解 HTTP、HTML 及基本的 Python 编程,就能快速入门。本教程面向“零基础”“小白”用户,讲解从最基本的抓取到进阶框架、异步、分布式再到反爬策略,逐步深入,手把手指导你搭建完整爬虫,并总结截至 2025 年最常用的 Python 爬虫库。
本教程特色
requests + BeautifulSoup 开始,到 Scrapy、Selenium、Playwright、异步爬虫,一步步掌握。温馨提示:
robots.txt 以及相关法律法规,避免给他人服务器带来不必要的压力。requests、httpx、aiohttp 等库向目标 URL 发送 GET、POST 请求,并获取响应。robots.txt(通常在 https://example.com/robots.txt),遵从抓取规则;time.sleep)、并发数限制;Windows:
前往 https://www.python.org/downloads 下载对应 3.8+ 的安装包,默认选中“Add Python 3.x to PATH”,点击“Install Now”。
安装完成后,打开命令行(Win + R → 输入 cmd → 回车),执行:
python --version
pip --version确认 Python 与 pip 已成功安装。
macOS:
建议使用 Homebrew 安装:
brew install python@3.10安装完成后,执行:
python3 --version
pip3 --version确认无误后即可。
Linux (Ubuntu/Debian 系):
sudo apt update
sudo apt install python3 python3-pip python3-venv -y执行:
python3 --version
pip3 --version即可确认。
提示:如果你机器上同时安装了 Python 2.x 和 Python 3.x,可能需要使用
python3、pip3来替代python、pip。
为了避免全局依赖冲突,强烈建议为每个爬虫项目创建独立的虚拟环境:
# 进入项目根目录
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),此时安装的所有包都只作用于该环境。
pdb。在虚拟环境中,执行:
pip install requests beautifulsoup4 lxmlrequests:Python 最常用的 HTTP 库,用于发送 GET/POST 请求。beautifulsoup4:常见的 HTML/XML 解析库,入门简单。lxml:速度快、功能强大的解析器,供 BeautifulSoup 使用。url:请求地址。params:URL 参数(字典/字符串)。headers:自定义请求头(例如 User-Agent、Referer、Cookie)。data / json:POST 请求时发送的表单或 JSON 数据。timeout:超时时间(秒),防止请求一直卡住。proxies:配置代理(详见后文)。示例:
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 字符下面以爬取「https://www.example.com」网页标题为例,演示最简单的流程:
# 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> 标签内容
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}')运行结果示例:
(venv) $ python simple_spider.py
网页标题:Example DomainBeautifulSoup 库使用简单,常用方法如下:
创建对象
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)、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 等快捷属性。示例:提取列表页所有文章链接
html = response.text
soup = BeautifulSoup(html, 'lxml')
# 假设每篇文章链接都在 <h2 class="post-title"><a href="...">...</a></h2>
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)CSV 格式
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 格式
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 存储(适合小规模项目)
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()User-Agent 检测
requests 的 User-Agent 大多被识别为“爬虫”,容易被屏蔽。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 限制
Cookie 验证
requests.Session() 管理会话,同一 Session 自动保存并发送 Cookie。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')验证码
AJAX / 动态渲染
requests 只能获取静态 HTML。虽然 BeautifulSoup 足以应付大部分新手场景,但当你遇到结构复杂、嵌套多、或需要批量高效提取时,下面这些工具会更适合。
特点:基于 C 语言实现,解析速度快,支持标准的 XPath 查询。
安装:
pip install lxml示例:
from lxml import etree
html = '''<html><body>
<div class="post"><h2><a href="/p1">文章A</a></h2></div>
<div class="post"><h2><a href="/p2">文章B</a></h2></div>
</body></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 的元素。特点:Scrapy 自带的一套基于 Css/XPath 的快速解析工具,接口与 lxml 类似,但更贴合 Scrapy 的数据提取习惯。
安装:
pip install parsel示例:
from parsel import Selector
html = '''<ul>
<li class="item"><a href="/a1">Item1</a></li>
<li class="item"><a href="/a2">Item2</a></li>
</ul>'''
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 中经常用到,直接拿过来在项目外部也能用。
特点:接口风格类似 jQuery,习惯了前端的同学会很快上手。
安装:
pip install pyquery示例:
from pyquery import PyQuery as pq
html = '''<div id="posts">
<h2><a href="/x1">新闻X1</a></h2>
<h2><a href="/x2">新闻X2</a></h2>
</div>'''
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。
正则并不是万能的 HTML 解析方案,但在提取简单规则(如邮箱、电话号码、特定模式字符串)时非常方便。
在爬虫中,可先用 BeautifulSoup/lxml 找到相应的大块内容,再对内容字符串用正则提取。
示例:
import re
from bs4 import BeautifulSoup
html = '''<div class="info">
联系邮箱:abc@example.com
联系电话:123-4567-890
</div>'''
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)如果你想快速搭建一个可维护、可扩展的爬虫项目,Scrapy 是 Python 爬虫生态中最成熟、最流行的爬虫框架之一。
安装 Scrapy:
pip install scrapy创建 Scrapy 项目:
scrapy startproject myproject项目目录结构(示例):
myproject/
scrapy.cfg # 部署时使用的配置文件
myproject/ # 项目 Python 模块
__init__.py
items.py # 定义数据模型(Item)
middlewares.py # 自定义中间件
pipelines.py # 数据处理与存储 Pipeline
settings.py # Scrapy 全局配置
spiders/ # 各种爬虫文件放在这里
__init__.py
example_spider.py假设我们要爬去 quotes.toscrape.com 网站上所有名言及作者:
在 myproject/spiders/ 下新建 quotes_spider.py:
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):
import scrapy
class MyprojectItem(scrapy.Item):
text = scrapy.Field()
author = scrapy.Field()
tags = scrapy.Field()配置数据存储 Pipeline(可选存储到 JSON/CSV/数据库),如在 myproject/pipelines.py:
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 中启用:
ITEM_PIPELINES = {
'myproject.pipelines.JsonWriterPipeline': 300,
}运行爬虫:
scrapy crawl quotes运行后,会在项目根目录生成 quotes.json,其中包含抓取到的所有名言数据。
items.py):定义要提取的数据结构与字段,相当于“数据模型”。spiders/xxx.py):每个 spider 文件对应一个任务,可接收 start_urls、allowed_domains、parse() 回调等。可自定义不同的回调函数来解析不同页面。pipelines.py):处理从 Spider 返回的 Item,常见操作包括数据清洗(去重、格式化)、存储(写入 JSON/CSV、入库)、下载附件等。settings.py):全局配置文件,包含并发数(CONCURRENT_REQUESTS)、下载延时(DOWNLOAD_DELAY)、中间件配置、管道配置、User-Agent 等。常见 Settings 配置示例:
# 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'Scrapy 提供了 scrapy shell <URL> 命令,可以快速测试 XPath、CSS 选择器。
scrapy shell 'https://quotes.toscrape.com/'进入 shell 后,你可以执行:
>>> 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 再跑要高效很多。
settings.py 中设置 CONCURRENT_REQUESTS(默认 16);CONCURRENT_REQUESTS_PER_DOMAIN(默认 8);CONCURRENT_REQUESTS_PER_IP;DOWNLOAD_DELAY(默认 0);AUTOTHROTTLE_ENABLED = True,配合 AUTOTHROTTLE_START_DELAY、AUTOTHROTTLE_MAX_DELAY 等。Downloader Middleware:位于 Scrapy 引擎与下载器之间,可控制请求/响应,常用于:
示例:随机 User-Agent Middleware
# 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 中配置:
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 等。
当目标网页内容依赖 JavaScript 动态渲染时,单纯用 requests 或 Scrapy 获取到的 HTML 往往不包含最终可视化的数据。此时可以使用“浏览器自动化”工具,让其像真实浏览器一样加载页面,再提取渲染后的内容。
安装:
pip install selenium下载 WebDriver(以 Chrome 为例):
chromedriver。chromedriver 放置在系统 PATH 下,或在代码中指定路径。示例:抓取动态网页内容
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,例如:
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'))
)Playwright:由微软维护、继承自 Puppeteer 的跨浏览器自动化库,支持 Chromium、Firefox、WebKit,无需单独下载 WebDriver。
优点:启动速度快、API 简洁、并发控制更灵活。
安装:
pip install playwright
# 安装浏览器内核(只需第一次执行)
playwright install示例:抓取动态内容
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:
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()--headless 参数;在 macOS/Windows 上也可加速启动。
chrome_options.add_argument('--disable-gpu')、--no-sandbox、--disable-dev-shm-usage;browser = await p.chromium.launch(headless=True, args=['--disable-gpu', '--no-sandbox'])。一般流程:
page_source 或 content();示例综合:
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()当面对上千个、甚至上万个链接需要同时抓取时,同步阻塞式的 requests 就显得效率低下。Python 原生的 asyncio 协程、aiohttp 库或 httpx 异步模式可以极大提升并发性能。
安装:
pip install aiohttp使用 asyncio + aiohttp 并发抓取
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(*) 等待所有任务完成。如果需要对抓取和解析做更精细的并行控制,可使用 asyncio.Semaphore 或第三方协程池库(如 aiomultiprocess、aiojobs)来控制并发数。
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))HTTPX:由 Encode 团队开发,与 requests API 十分相似,同时支持同步与异步模式。
安装:
pip install httpx示例:
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 等),极大降低了上手门槛。
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))爬虫的最终目的是获取并存储有价值的数据,因此选择合适的存储方式与去重机制至关重要。
CSV/JSON:
SQLite:
轻量级嵌入式数据库,无需额外部署数据库服务器。
适合中小规模项目,比如几万条数据。
示例:
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()优点:适合大规模数据存储,支持 SQL 强大的查询功能,能更好地做数据分析、统计。
安装:先安装对应数据库服务器(MySQL、MariaDB、PostgreSQL),然后在 Python 中安装驱动:
pip install pymysql # MySQL
pip install psycopg2 # PostgreSQL示例(MySQL):
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()优点:文档型数据库,对半结构化 JSON 数据支持友好,可灵活存储字段不同的条目。
安装与驱动:
pip install pymongo。示例:
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)Redis:键值存储,支持超高并发访问,非常适合做指纹去重、短期缓存、队列等。
常见策略:
pybloom-live 或直接在 Redis 中搭建 Bloom Filter(如 RedisBloom 模块)。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 已存在,跳过')pybloom-live:纯 Python 布隆过滤器库;redis-py-bloom 或 Redis 官方 RedisBloom 模块(需 Redis 安装相应扩展);scrapy.dupefilters.RFPDupeFilter,默认用的是文件或 Redis 存储的指纹去重。当单机爬虫难以满足高并发、大规模抓取时,就需要分布式爬虫,将任务分布到多台机器协同完成。Scrapy-Redis 是 Scrapy 官方推荐的分布式方案之一。
Scrapy-Redis:基于 Redis 存储队列与去重指纹,实现分布式调度、分布式去重、数据共享的 Scrapy 扩展。
安装:
pip install scrapy-redis在 Scrapy 项目中集成 Scrapy-Redis
修改 settings.py:
# 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。# 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 推入列表:
redis-cli
lpush redis_quotes:start_urls "https://quotes.toscrape.com/"启动分布式爬虫
在多台服务器或多终端分别启动爬虫:
scrapy crawl redis_quotes所有实例会从同一个 Redis 队列中获取 URL,去重也基于 Redis,互不重复。
部署多台服务器(A、B、C),都能访问同一个 Redis 实例。
在 A 机上运行:
redis-server # 启动 Redis(可独立部署)在 A、B、C 机上,各自拉取完整的 Scrapy 项目代码,并配置好 settings.py 中的 REDIS_URL。
在 A 机或任意一处,将种子 URL 塞入 Redis:
redis-cli -h A_ip -p 6379 lpush redis_quotes:start_urls "https://quotes.toscrape.com/"在 A、B、C 分别运行:
scrapy crawl redis_quotes数据收集:
RedisPipeline 自动存入 Redis 列表(key: quotes:items);访问频率控制(限速)
对目标站设置随机或固定延时:
import time, random
time.sleep(random.uniform(1, 3)) # 随机等待 1~3 秒Scrapy 中使用 DOWNLOAD_DELAY、AUTOTHROTTLE_ENABLED 等。
User-Agent 伪装
Referer、Accept-Language、Accept-Encoding 等 Headers
模拟真实浏览器请求时携带的完整 Header:
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)Session 对象:在 requests 中,使用 requests.Session() 方便统一管理 Cookie。
模拟登录流程:
GET 请求,拿到隐藏的 token(如 CSRF);POST 到登录接口;session 内部有了 Cookie,后续使用同一 session 发起请求即可保持登录状态。带 Cookie 抓取:
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)常见验证码类型:
常用方案:
简单 OCR 识别:用 pytesseract 对简单数字/字母验证码进行识别,但对扭曲度高或干扰线多的验证码成功率不高。
pip install pytesseract pillowfrom PIL import Image
import pytesseract
img = Image.open('captcha.png')
text = pytesseract.image_to_string(img).strip()
print('识别结果:', text)打码平台/人工打码:当验证码过于复杂时,可调用第三方打码平台 API(如超级鹰、打码兔等),将图片发送给平台,由平台返回识别结果;或者简单地由人工识别。
绕过/获取接口:很多网站的登录并不真用验证码进行提交,而是在前端校验。可以抓包找到真实的登录接口,模拟接口请求,绕过验证码。
为什么要用代理
获取代理
搭建本地简单代理池示例(以免费代理为例,仅供学习)
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 中设置:
# 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:
# 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 中取一个代理。
本节以“爬取某模拟新闻网站(示例:https://news.example.com)的头条新闻,并将标题、摘要、链接存入 MySQL 数据库”为例,完整演示 Scrapy + MySQL 的使用。
headline_news,字段:id, title, summary, url, pub_date。创建 Scrapy 项目
scrapy startproject news_spider
cd news_spider安装依赖
pip install scrapy pymysql定义 Item (news_spider/items.py)
import scrapy
class NewsSpiderItem(scrapy.Item):
title = scrapy.Field()
summary = scrapy.Field()
url = scrapy.Field()
pub_date = scrapy.Field()设置 MySQL 配置 (news_spider/settings.py)
# 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)
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)
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)
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):
# 假设首页头条新闻在 <div class="headline-list"> 下,每个新闻项 <div class="item">
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}
)
# 假设分页结构:下一页链接在 <a class="next-page" href="...">
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,用户名、密码正确;
在项目根目录执行:
scrapy crawl news运行期间,日志会显示抓取进度,成功后可在 headline_news 表中查看抓取结果:
SELECT * FROM headline_news LIMIT 10;parse 方法中发起新的 Request 到详情页?
meta 参数可将部分已抓取的字段传递到下一个回调。'2025-05-30 14:30:00' 转为 datetime?
datetime.strptime,传入对应格式;若格式不一致,可先 strip() 或正则提取。start_requests 方法里模拟登录(使用 requests + cookies 或 Selenium),登录后获取 Cookie,再将 Cookie 带入 Scrapy 调用。page=1,2,3...),使用 for page in range(1, 1001): yield scrapy.Request(...)。注意限速与 IP 轮换,防止被封。DOWNLOADER_MIDDLEWARES 中配置自己的 RandomProxyMiddleware,或直接使用 Scrapy-Proxy-Pool 等库。以下对各类常用库进行分类归纳,并附简要说明与典型使用场景。
库 名 | 功能简介 | 典型场景 |
|---|---|---|
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 时 |
库 名 | 功能简介 | 典型场景 |
|---|---|---|
Selenium | 最成熟的浏览器自动化框架,支持 Chrome、Firefox、Edge 等 | 需模拟用户操作 (点击、滑动、表单提交)、抓取 JS 渲染内容 |
Playwright | 微软出品,继承 Puppeteer,API 简洁,支持多浏览器 | 高性能 headless 模式,异步/同步模式都支持 |
Pyppeteer | Puppeteer 的 Python 移植版 | Node.js 用户转 Python 时快速上手 |
undetected-chromedriver | 对抗反爬,屏蔽 Selenium 特征 | 需要更强的逃避检测能力,尤其面对高级反爬 |
Splash | 由 Scrapy-Splash 提供,基于 QtWebKit 的渲染服务 | Scrapy 与动态渲染结合,用于批量异步渲染 |
库 名 | 功能简介 | 典型场景 |
|---|---|---|
asyncio | Python 标准库,提供事件循环与异步协程基础 | 编写异步爬虫主框架 |
aiohttp | 基于 asyncio 的 HTTP 客户端 | 高并发抓取、配合 BeautifulSoup/lxml 解析 |
httpx | 支持同步 & 异步,与 requests 接口兼容 | 希望无缝从 requests 切换到异步模式 |
trio | 另一个异步框架,示意图结构友好,但生态相对较小 | 深度研究异步原理或希望新尝试 |
curio | 纯 Python 异步库,强调简洁 | 研究异步 I/O 原理的场景 |
aiofiles | 异步文件操作 | 异步模式下同时要读写大量文件 |
库 名 | 功能简介 | 典型场景 |
|---|---|---|
requests + Session | 模拟登录,自动管理 Cookie | 大部分需要登录后抓取的场景 |
selenium | 浏览器自动化登录,执行 JS,处理复杂登录逻辑 | 登录时有 JS 加密或动态 token |
Playwright | 与 Selenium 类似,但速度更快,接口更现代 | 更轻量级的浏览器自动化 |
pytesseract | OCR 识别图片文字 | 简单验证码识别 |
captcha_solver | 第三方打码平台 SDK | 需要调用付费打码平台处理验证码 |
twoCaptcha | 付费打码平台 Python 客户端 | 需要可靠的验证码打码服务 |
库 名 | 功能简介 | 典型场景 |
|---|---|---|
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 秒验证页面 |
库 名 | 功能简介 | 典型场景 |
|---|---|---|
scrapy-redis | Scrapy 分布式爬虫扩展,统一 Redis 作为队列与去重存储 | 分布式 Scrapy 项目 |
scrapy-cluster | 基于 Kafka + Redis 的 Scrapy 分布式爬虫系统 | 企业级分布式环境,需与消息队列协同 |
Frigate | 高性能分布式爬虫,结合 Redis + MongoDB | 大规模分布式爬取且需要与 NoSQL 存储集成 |
PhantomJS + Splash | 无头浏览器渲染服务,可与 Scrapy 搭配形成分布式渲染环境 | 需要大规模渲染 JS 页面后再抓取 |
库 名 | 功能简介 | 典型场景 |
|---|---|---|
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 爬虫库,后续可能有新库或旧库迭代,请根据实际需求及时查阅官方文档或社区资源。
ModuleNotFoundError: No module named 'xxx'
pip install xxx。requests.exceptions.SSLError: [SSL: CERTIFICATE_VERIFY_FAILED]
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 返回值数量与预期不符。zip() 将两个列表匹配。selenium.common.exceptions.WebDriverException: Message: 'chromedriver' executable needs to be in PATH
chromedriver 未放在系统 PATH,或路径不正确。chromedriver,并将其路径添加到环境变量,或者在代码中指定 executable_path。pymysql.err.OperationalError: (1045, "Access denied for user 'root'@'localhost' (using password: YES)")
TimeoutError 或 asyncio.exceptions.TimeoutError
timeout 参数,降低并发数,适当设置代理。response.encoding = 'utf-8',或者在读写文件时加 encoding='utf-8'。状态码 | 含义 |
|---|---|
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,服务器暂时无法处理请求,常见于流量过大被限流 |
本教程从最基础的 requests + BeautifulSoup,到 Scrapy 框架、浏览器自动化、异步爬虫、分布式爬虫,系统梳理了 Python 爬虫的常见技术与实践要点,并盘点了截至 2024 年底的主流库与工具。对于初学者而言,掌握以下几个关键点即可快速上手:
最后,爬虫技术更新迅速,截止到本教程编写时(2024 年底)的主流库可能会随着技术迭代、站点反爬升级而发生变化。建议你在入门后,积极关注各大 Python 社区、GitHub Trending 以及官方文档,及时跟进新特性、新库、新思路,不断优化自己的爬虫方案。祝你能在数据抓取的道路上越走越远,愉快地玩转 Python 爬虫世界!
创作时间:2025 年 6 月 1 日