跳到主要内容
Python 爬虫从入门到实战:Requests、Scrapy、异步与反爬 | 极客日志
Python AI
Python 爬虫从入门到实战:Requests、Scrapy、异步与反爬 这篇内容把 Python 爬虫的主线串了起来:从 requests + BeautifulSoup 入门,到 lxml、parsel、PyQuery 等解析工具,再到 Scrapy 项目结构、Selenium/Playwright 动态渲染、aiohttp 和 HTTPX 异步抓取,以及 MySQL、MongoDB、Redis、Bloom Filter 的存储与去重方案。文章还补了反爬常见手段、Scrapy-Redis 分布式调度和一个 Scrapy + MySQL 的完整案例,整体结论很明确:小任务先用 Requests,项目化用 Scrapy,动态页面看接口优先,抓取规模上来后再考虑异步和分布式。
前言
如果你第一次接触爬虫,最容易被'全栈''大全''一篇搞定'这类标题带偏。真做起来其实没那么玄,核心就三件事:发请求、解析内容、把数据存下来。剩下的 Requests、BeautifulSoup、Scrapy、Selenium、Playwright、aiohttp,不过是把这三件事做得更稳、更快,或者更像浏览器一点。
这篇内容按实际开发里常见的路径来写:先用 requests + BeautifulSoup 把基础打牢,再看 lxml、parsel、PyQuery 这类解析工具;如果页面是 JavaScript 动态渲染,就上 Selenium 或 Playwright;抓取量一大,异步和分布式就绕不开;最后再把存储、去重和反爬这些容易踩坑的地方串起来。示例都基于 Python 3.8+,更建议直接用 Python 3.10 以上,少碰兼容性问题。
爬取网站数据时,先看 robots.txt 和站点规则。能走公开接口就别硬怼页面,省事,也更不容易把自己卡死在反爬上。
1. 爬虫到底在做什么
爬虫并不复杂。它本质上就是一个程序:
请求一个 URL;
拿到 HTML、JSON、图片或其他响应;
从里面提取需要的数据;
清洗、去重、存储;
继续翻页或沿着链接往下抓。
常见场景也很直接:电商比价、舆情监控、招聘信息采集、学术数据整理、内容聚合。搜索引擎索引网页也是同一类事情,只是规模大得多。
做爬虫时,很多问题不是'会不会写代码',而是'这一步到底该不该做'。比如动态页面,优先看接口;能直接拿 JSON,就别开浏览器。这个判断通常比堆工具更重要。
1.1 先把边界说清楚
请求前先看 robots.txt,别默认所有页面都能抓。
有些站点会限制商业用途或批量访问,别忽略条款。
控制频率,别把请求打得像压测。
数据的后续使用也要注意合规,别拿'学习用途'当借口。
2. 开发环境
2.1 安装 Python
建议直接装 Python 3.10 或更高版本。
Windows 去 https://www.python.org/downloads 下载对应安装包,安装时勾上'Add Python 3.x to PATH'。
macOS 可以用 Homebrew:
brew install [email protected]
Linux(Ubuntu/Debian)常用这组:
sudo apt update && sudo apt install python3 python3-pip python3-venv -y
python3 --version
pip3 --version
如果机器里同时有 Python 2 和 Python 3,后面大多数命令都建议显式写 python3、pip3。
2.2 虚拟环境
爬虫项目很容易把依赖装乱。单独建一个虚拟环境,后面省很多事。
mkdir my_spider && cd my_spider
python3 -m venv venv
venv\Scripts\activate
source venv/bin/activate
激活后安装的包只会落在这个环境里。这个习惯值得保留,尤其是同时折腾 Selenium、Playwright、Scrapy 的时候。
2.3 常用工具
编辑器/IDE:PyCharm、VS Code 都够用。
调试:VS Code / PyCharm 自带调试器,或者直接用 pdb。
抓包和接口查看:Postman、Insomnia、Charles、Fiddler。
版本管理:Git。
3. 基础抓取:Requests + BeautifulSoup
3.1 安装 pip install requests beautifulsoup4 lxml
requests 负责发请求,BeautifulSoup 负责解析 HTML,lxml 提供更快的解析器。
3.2 一个最小请求示例 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)
print (response.encoding)
print (response.text[:200 ])
请求里最常用的参数就是这些:params、headers、data、json、timeout、proxies。很多时候页面抓不到,不是库不行,而是头没带对,或者超时没处理。
3.3 第一个爬虫:抓网页标题 import requests
from bs4 import BeautifulSoup
def fetch_title (url ):
try :
headers = {'User-Agent' : 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) ...' }
response = requests.get(url, headers=headers, timeout=10 )
response.raise_for_status()
response.encoding = response.apparent_encoding
soup = BeautifulSoup(response.text, 'lxml' )
title_tag = soup.find('title' )
if title_tag:
return title_tag.get_text().strip()
return '未找到 title 标签'
except Exception as e:
return f'抓取失败:{e} '
if __name__ == '__main__' :
url = 'https://www.example.com'
title = fetch_title(url)
print (f'网页标题:{title} ' )
运行后会输出 Example Domain。这个例子没什么花活,但把流程跑通了:请求、编码、解析、提取。
3.4 BeautifulSoup 常用法 soup.find('div' , class_='content' )
soup.find_all('a' , href=True )
soup.select('div.content > ul li a' )
node.get('href' )
node.get_text(strip=True )
适合新手,也适合小项目。页面结构不算特别乱的时候,BeautifulSoup 足够顺手。
3.5 存储 小项目常见的落地方式是 CSV、JSON、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
conn.commit()
conn.close()
CSV 和 JSON 也很常用,尤其是临时导出或交给别的脚本处理的时候。
3.6 常见反爬应对
User-Agent:默认请求头太像脚本,换成浏览器 UA。
Cookie:登录后才能看的内容,通常要靠 requests.Session() 保持会话。
频率限制:别一口气打太快,time.sleep() 很朴素,但有时真管用。
动态加载:先看接口,别急着上浏览器。
import requests
session = requests.Session()
login_data = {'username' : 'xxx' , 'password' : 'xxx' }
session.post('https://example.com/login' , data=login_data)
response = session.get('https://example.com/protected-page' )
4. 解析工具:lxml、parsel、PyQuery、正则
4.1 lxml + XPath from lxml import etree
html = '''<html><body>
<div><h2><a href="/p1">文章 A</a></h2></div>
<div><h2><a href="/p2">文章 B</a></h2></div>
</body></html>'''
tree = etree.HTML(html)
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)
4.2 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)
for item in sel.css('li.item' ):
title = item.css('a::text' ).get()
link = item.css('a::attr(href)' ).get()
print (title, link)
4.3 PyQuery 如果你平时写前端,PyQuery 会更顺手一点,语感像 jQuery。
from pyquery import PyQuery as pq
html = '''<div>
<h2><a href="/x1">新闻 X1</a></h2>
<h2><a href="/x2">新闻 X2</a></h2>
</div>'''
doc = pq(html)
for item in doc('#posts h2' ):
a = pq(item).find('a' )
title = a.text()
url = a.attr('href' )
print (title, url)
4.4 正则 正则不适合做主力 HTML 解析,但提邮箱、电话、固定格式字符串时很方便。通常我会先用解析器定位大块内容,再用正则做最后一层筛选。
import re
from bs4 import BeautifulSoup
html = '''<div> 联系邮箱:[email protected] 联系电话:123-4567-890 </div>'''
soup = BeautifulSoup(html, 'lxml' )
info = soup.find('div' ).get_text()
email_pattern = r'[\w\.-]+@[\w\.-]+'
emails = re.findall(email_pattern, info)
phone_pattern = r'\d{3}-\d{4}-\d{3,4}'
phones = re.findall(phone_pattern, info)
print ('邮箱:' , emails)
print ('电话:' , phones)
5. Scrapy:适合做项目的框架 如果只是抓十几个页面,Requests 就够了。要是页面多、规则多、需要管道和中间件,Scrapy 会省很多结构化的工作。
5.1 安装和项目结构 pip install scrapy
scrapy startproject myproject
myproject/
scrapy.cfg
myproject/
__init__.py
items.py
middlewares.py
pipelines.py
settings.py
spiders/
__init__.py
example_spider.py
5.2 一个简单 Spider 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)
5.3 Item、Pipeline、Settings Item 定义字段,Pipeline 负责清洗和存储,settings.py 管全局配置。这个拆分看起来繁琐,但项目一旦超过一两个页面,维护成本差别就出来了。
BOT_NAME = 'myproject'
SPIDER_MODULES = ['myproject.spiders' ]
NEWSPIDER_MODULE = 'myproject.spiders'
ROBOTSTXT_OBEY = True
CONCURRENT_REQUESTS = 8
DOWNLOAD_DELAY = 1
DEFAULT_REQUEST_HEADERS = {
'User-Agent' : 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) ...' ,
}
5.4 Scrapy Shell 调试提取规则时,scrapy shell <URL> 比反复跑完整爬虫高效得多。
scrapy shell 'https://quotes.toscrape.com/'
response.css('div.quote span.text::text' ).getall()
response.xpath('//div[@class="quote"]/span[@class="text"]/text()' ).getall()
5.5 中间件和并发 Scrapy 的中间件适合做 UA、代理、重试、重定向这类横切逻辑。并发参数也都能在 settings.py 里调:
CONCURRENT_REQUESTS = 8
CONCURRENT_REQUESTS_PER_DOMAIN = 4
DOWNLOAD_DELAY = 1
AUTOTHROTTLE_ENABLED = True
这个层面上,Scrapy 的价值不是'能不能抓',而是'抓得乱的时候还能收拾得住'。
6. 动态页面:Selenium 和 Playwright 页面内容靠 JavaScript 渲染时,直接拿 HTML 往往是空壳。这个时候才轮到浏览器自动化上场。
6.1 Selenium from selenium import webdriver
from selenium.webdriver.chrome.service import Service as ChromeService
from selenium.webdriver.chrome.options import Options
from bs4 import BeautifulSoup
import time
chrome_options = Options()
chrome_options.add_argument('--headless' )
chrome_options.add_argument('--no-sandbox' )
chrome_options.add_argument('--disable-gpu' )
service = ChromeService(executable_path='path/to/chromedriver' )
driver = webdriver.Chrome(service=service, options=chrome_options)
try :
driver.get('https://quotes.toscrape.com/js/' )
time.sleep(2 )
html = driver.page_source
soup = BeautifulSoup(html, 'lxml' )
for quote in soup.select('div.quote' ):
text = quote.find('span' , class_='text' ).get_text()
author = quote.find('small' , class_='author' ).get_text()
print (text, author)
finally :
driver.quit()
Selenium 的好处是成熟,坏处也明显:启动重、依赖 WebDriver,跑多了不算轻。
6.2 Playwright Playwright 更现代一些,启动和等待都更顺手。
pip install playwright
playwright install
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()
6.3 什么时候别开浏览器 如果页面只是接口返回数据,浏览器自动化就有点重了。能直接请求 API,就别用 Selenium 或 Playwright;这不是偷懒,是减少不必要的失败点。
7. 异步爬虫:aiohttp 和 HTTPX 抓很多链接的时候,同步请求会被网络等待拖住。异步的意义就在这儿:不是单次请求更快,而是等待期间还能继续干别的。
7.1 aiohttp import asyncio
import aiohttp
from bs4 import BeautifulSoup
async def fetch (session, url ):
try :
async with session.get(url, timeout=10 ) as response:
return await response.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 ):
conn = aiohttp.TCPConnector(limit=50 )
async with aiohttp.ClientSession(connector=conn) as session:
tasks = [asyncio.create_task(fetch(session, url)) for url in urls]
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))
7.2 HTTPX HTTPX 的接口和 Requests 很像,迁移成本低。
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))
异步的坑也很实在:并发不是越大越好,链接数、服务端限制、DNS、超时、连接池,都会影响结果。这里通常要靠一点试出来的经验,不是写个 gather 就结束了。
8. 存储和去重 抓到了数据,不代表项目就结束了。真正麻烦的是:重复 URL 怎么办,字段缺失怎么补,数据往哪里放。
8.1 常见存储
CSV / JSON:轻量,适合导出。
SQLite:本地小中型项目很合适。
MySQL / PostgreSQL:适合结构化、持续写入的数据。
MongoDB:字段不稳定、JSON 风格数据多的时候比较顺手。
Redis:去重、缓存、队列。
8.2 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()
8.3 Redis 去重 import redis
r = redis.Redis(host='localhost' , port=6379 , db=0 )
url = 'https://example.com/page/1'
if r.sadd('visited_urls' , url):
print ('新 URL,可爬取' )
else :
print ('URL 已存在,跳过' )
8.4 Bloom Filter 如果 URL 数量大到普通集合撑不住,Bloom Filter 是更现实的选择。它不是绝对准确,但能用很少的内存换到很高的去重效率。缺点也很明确:会有误判。
9. 分布式爬虫:Scrapy-Redis 单机跑不动的时候,通常不是代码写得不够'高级',而是任务规模已经超出单点能力了。Scrapy-Redis 的作用就是把调度和去重放到 Redis 里,多台机器一起干活。
9.1 安装
9.2 基本思路
Redis 里放种子 URL;
多个爬虫实例同时消费;
去重也交给 Redis;
Item 可以进 Redis,再由别的进程落库。
from scrapy_redis.spiders import RedisSpider
from myproject.items import MyprojectItem
class RedisQuotesSpider (RedisSpider ):
name = 'redis_quotes'
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)
SCHEDULER = 'scrapy_redis.scheduler.Scheduler'
SCHEDULER_PERSIST = True
DUPEFILTER_CLASS = 'scrapy_redis.dupefilter.RFPDupeFilter'
REDIS_URL = 'redis://:[email protected] :6379/0'
10. 反爬:别把问题想得太玄 很多反爬策略其实就几类:限频、校验头、校验 Cookie、验证码、动态接口、IP 限制。别上来就默认对方用了什么'高级风控',先看响应和网络请求。
10.1 请求头和频率 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' : 'sessionid=xxx; other=yyy' ,
}
import time, random
time.sleep(random.uniform(1 , 3 ))
10.2 登录和 Cookie session = requests.Session()
login_page = session.get('https://example.com/login' )
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' : '...' })
profile = session.get('https://example.com/profile' )
print (profile.text)
10.3 验证码 简单验证码可以试 OCR,复杂的滑块、点选验证码通常没必要在入门阶段硬啃。很多场景下,直接找接口比识别验证码更稳。
pip install pytesseract pillow
from PIL import Image
import pytesseract
img = Image.open ('captcha.png' )
text = pytesseract.image_to_string(img).strip()
print ('识别结果:' , text)
10.4 代理池 代理池的作用不是'隐身',只是把单个 IP 的压力分散掉。免费代理能用,但稳定性一般;付费代理省心很多。
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
11. 一个完整案例:Scrapy 抓新闻并入库 下面这个案例的重点不是'新闻站'本身,而是把前面几块串起来:Spider、详情页、Pipeline、MySQL、随机 UA。
11.1 需求
抓标题、摘要、链接、发布时间;
先抓列表页,再进详情页补字段;
写入 MySQL;
控制并发和频率。
11.2 Spider 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 ):
from datetime import datetime
try :
return datetime.strptime(date_str, '%Y-%m-%d %H:%M:%S' )
except :
return None
11.3 MySQL Pipeline 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()
self .cursor.execute("""
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 .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:
pass
return item
11.4 配置 MYSQL_HOST = 'localhost'
MYSQL_PORT = 3306
MYSQL_USER = 'root'
MYSQL_PASSWORD = 'root'
MYSQL_DB = 'news_db'
MYSQL_CHARSET = 'utf8mb4'
ITEM_PIPELINES = {
'news_spider.pipelines.MySQLPipeline' : 300 ,
}
ROBOTSTXT_OBEY = True
DOWNLOAD_DELAY = 1
CONCURRENT_REQUESTS = 8
12. 常用库列表 库名 作用 适合的场景 requests 同步 HTTP 请求 大多数简单爬虫 httpx 同步/异步 HTTP 客户端 需要兼容 Requests 风格或异步 aiohttp asyncio 异步请求 高并发抓取 BeautifulSoup HTML/XML 解析 入门和中小项目 lxml 高性能解析 + XPath 快速提取、大量页面 parsel Scrapy 风格解析 Scrapy 项目或类似写法 PyQuery jQuery 风格解析 前端思维更顺手 Selenium 浏览器自动化 需要真实浏览器行为 Playwright 现代浏览器自动化 动态页面、自动化测试、抓取 scrapy 爬虫框架 结构化项目 scrapy-redis 分布式调度 多机协作 pymysql MySQL 驱动 MySQL 入库 pymongo MongoDB 驱动 文档型存储 redis 缓存/去重/队列 指纹去重、任务调度
13. 常见报错
13.1 ModuleNotFoundError: No module named 'xxx' 一般就是没装,或者装在别的环境里了。先确认虚拟环境有没有激活。
13.2 requests.exceptions.SSLError 证书校验问题,先升级 certifi,再看是不是本机环境异常。
13.3 chromedriver executable needs to be in PATH ChromeDriver 路径不对,或者版本不匹配。
13.4 MySQL Access denied
13.5 TimeoutError 通常是网络慢、并发太高、或者目标站限流。别只盯着超时时间,连接数也要一起看。
14. 收尾 如果只记一个最重要的判断,我会选这个:先请求,再解析;先接口,再页面;先稳定,再优化。
对初学者来说,最稳的路线其实很朴素:Requests 把 HTTP 搞明白,BeautifulSoup 或 lxml 把页面结构弄清楚,Scrapy 负责把项目化的东西收拢起来,动态页面再补 Selenium 或 Playwright,量大了就上异步或分布式。别一开始就追求'覆盖所有库',那样通常只会把自己绕晕。
爬虫这件事,真到线上时拼的不是炫技,而是判断:这个站能不能抓、该不该抓、怎么抓才不容易坏。
相关免费在线工具 RSA密钥对生成器 生成新的随机RSA私钥和公钥pem证书。 在线工具,RSA密钥对生成器在线工具,online
Mermaid 预览与可视化编辑 基于 Mermaid.js 实时预览流程图、时序图等图表,支持源码编辑与即时渲染。 在线工具,Mermaid 预览与可视化编辑在线工具,online
随机西班牙地址生成器 随机生成西班牙地址(支持马德里、加泰罗尼亚、安达卢西亚、瓦伦西亚筛选),支持数量快捷选择、显示全部与下载。 在线工具,随机西班牙地址生成器在线工具,online
curl 转代码 解析常见 curl 参数并生成 fetch、axios、PHP curl 或 Python requests 示例代码。 在线工具,curl 转代码在线工具,online
Base64 字符串编码/解码 将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
Base64 文件转换器 将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online