跳到主要内容Python 爬虫实战:反爬绕过与数据解析常见问题总结 | 极客日志Python
Python 爬虫实战:反爬绕过与数据解析常见问题总结
本文总结了 Python 爬虫开发中常见的五个技术坑点。包括 User-Agent 伪装、IP 封禁处理、BeautifulSoup 解析语法(class_)、JSON 编码乱码修复以及动态页面渲染(Selenium/Playwright)。针对每个问题提供了具体的代码示例和解决方案,帮助开发者避免常见错误。
Python 爬虫实战:反爬绕过与数据解析常见问题总结
第一个坑:User-Agent 被识别为爬虫
刚开始写爬虫时,直接用 requests.get(url) 发送请求,结果返回 403 Forbidden。
问题原因:requests 默认的 User-Agent 是 python-requests/版本号,网站一眼就能识别这是爬虫。
解决方案:自定义请求头,伪装成浏览器。
import requests
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
: ,
: ,
: ,
: ,
}
response = requests.get(url, headers=headers, timeout=)
(response.status_code)
'Accept'
'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8'
'Accept-Language'
'zh-CN,zh;q=0.9,en;q=0.8'
'Accept-Encoding'
'gzip, deflate'
'Connection'
'keep-alive'
10
print
经验:Headers 越完整越像真人在访问,最少也要加上 User-Agent。
第二个坑:IP 被封禁
搞定了 User-Agent 开始爬数据,结果爬了 100 多条后,又返回 403 了。这次更惨,换浏览器也不行——IP 被封了!
问题原因:同一个 IP 短时间内请求太频繁,触发了网站的反爬机制。
import requests
import time
import random
proxies = [
{'http': 'http://123.45.67.89:8080', 'https': 'https://123.45.67.89:8080'},
{'http': 'http://98.76.54.32:8080', 'https': 'https://98.76.54.32:8080'},
]
def fetch_with_proxy(url):
proxy = random.choice(proxies)
headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'}
try:
response = requests.get(url, headers=headers, proxies=proxy, timeout=10)
time.sleep(random.uniform(1, 3))
return response
except Exception as e:
print(f"请求失败:{e}")
return None
for page in range(1, 11):
url = f"https://example.com/list?page={page}"
response = fetch_with_proxy(url)
if response:
print(f"第 {page} 页爬取成功")
经验:免费代理不稳定,生产环境建议用付费代理服务。请求间隔是关键,别太贪心。
第三个坑:CSS 选择器解析不到数据
终于绕过了反爬,拿到 HTML 了。结果用 BeautifulSoup 解析时,什么都拿不到!
from bs4 import BeautifulSoup
html = '<div><span>商品名称</span></div>'
soup = BeautifulSoup(html, 'html.parser')
name = soup.find(class_='name').text
print(name)
问题原因:class 是 Python 关键字,BeautifulSoup 用 class_ 参数代替。
from bs4 import BeautifulSoup
html = '''
<div>
<div>
<span>iPhone 15</span>
<span>5999</span>
</div>
<div>
<span>华为 Mate 60</span>
<span>6999</span>
</div>
</div>
'''
soup = BeautifulSoup(html, 'html.parser')
items = soup.find_all(class_='product-item')
for item in items:
name = item.find(class_='name').text
price = item.find(class_='price').text
print(f"商品:{name}, 价格:{price}")
names = soup.select('.product-item .name')
prices = soup.select('.product-item .price')
for name, price in zip(names, prices):
print(f"商品:{name.text}, 价格:{price.text}")
经验:BeautifulSoup 的 class_ 参数容易忘,CSS 选择器 .class 更直观。
第四个坑:JSON 数据中文乱码
这次爬到了一个 API 接口,返回的是 JSON 数据。结果打印出来全是 \u4e2d\u6587 这种 Unicode 转义符,看得我头皮发麻。
import requests
response = requests.get('https://api.example.com/data')
data = response.json()
print(data)
response.encoding = 'utf-8'
data = response.json()
print(data)
问题原因:HTTP 响应头没有明确指定编码时,requests 默认用 ISO-8859-1 解析,导致中文乱码。
import requests
import json
def fetch_json(url):
response = requests.get(url)
response.encoding = response.apparent_encoding
try:
data = response.json()
return data
except json.JSONDecodeError:
print("JSON 解析失败,原始文本:", response.text[:200])
return None
url = 'https://api.example.com/users'
data = fetch_json(url)
if data:
for user in data:
print(f"用户名:{user.get('name','N/A')}")
经验:拿到 JSON 数据后,先 print(response.text[:200]) 看看原始内容,确认编码再解析。
第五个坑:异步加载的数据抓不到
最坑爹的情况来了:网页用 JavaScript 动态加载数据,requests 拿到的是空壳 HTML!
import requests
from bs4 import BeautifulSoup
url = 'https://example.com/dynamic-page'
response = requests.get(url)
soup = BeautifulSoup(response.text, 'html.parser')
items = soup.find_all(class_='product-item')
print(f"找到 {len(items)} 个商品")
问题原因:现代网页大量使用 AJAX、Vue、React 等技术,数据在浏览器中通过 JavaScript 动态渲染,requests 只能拿到最初的 HTML 骨架。
解决方案:使用 Selenium 或 Playwright 模拟浏览器行为。
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
import time
options = Options()
options.add_argument('--headless')
options.add_argument('--disable-gpu')
options.add_argument('--no-sandbox')
driver = webdriver.Chrome(options=options)
try:
driver.get('https://example.com/dynamic-page')
WebDriverWait(driver, 10).until(
EC.presence_of_element_located((By.CLASS_NAME, 'product-item'))
)
for _ in range(3):
driver.execute_script('window.scrollTo(0, document.body.scrollHeight);')
time.sleep(1)
items = driver.find_elements(By.CLASS_NAME, 'product-item')
print(f"找到 {len(items)} 个商品")
for item in items:
name = item.find_element(By.CLASS_NAME, 'name').text
price = item.find_element(By.CLASS_NAME, 'price').text
print(f"商品:{name}, 价格:{price}")
finally:
driver.quit()
import asyncio
from playwright.async_api import async_playwright
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://example.com/dynamic-page')
await page.wait_for_selector('.product-item')
items = await page.query_selector_all('.product-item')
for item in items:
name = await item.query_selector_eval('.name', 'el => el.textContent')
price = await item.query_selector_eval('.price', 'el => el.textContent')
print(f"商品:{name}, 价格:{price}")
await browser.close()
asyncio.run(main())
经验:遇到动态加载的页面,先右键查看网页源代码,如果搜不到数据,就是 JavaScript 渲染的,乘早用 Selenium/Playwright。
微信扫一扫,关注极客日志
微信公众号「极客日志」,在微信中扫描左侧二维码关注。展示文案:极客日志 zeeklog
相关免费在线工具
- curl 转代码
解析常见 curl 参数并生成 fetch、axios、PHP curl 或 Python requests 示例代码。 在线工具,curl 转代码在线工具,online
- Base64 字符串编码/解码
将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
- Base64 文件转换器
将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online
- Markdown 转 HTML
将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML 转 Markdown 互为补充。 在线工具,Markdown 转 HTML在线工具,online
- HTML 转 Markdown
将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML 转 Markdown在线工具,online
- JSON 压缩
通过删除不必要的空白来缩小和压缩JSON。 在线工具,JSON 压缩在线工具,online