1. 爬虫利器requests_html与json解析实战
作为一名爬虫开发者,我经常需要处理网页抓取和数据解析的工作。requests_html库是我工具箱中的重要成员,它完美结合了requests的简洁性和BeautifulSoup的解析能力,还能直接执行JavaScript。配合json模块处理API数据,可以解决90%的爬虫需求。下面我将分享这套组合拳的实战经验。
2. requests_html核心功能解析
2.1 与requests库的本质区别
requests_html不同于传统requests库的关键在于:
- 内置HTML解析器:直接通过
response.html访问解析后的DOM树 - JavaScript支持:通过
render()方法执行页面中的JS代码 - CSS选择器增强:支持更丰富的
find()方法选择元素 - XPath原生支持:无需额外安装lxml即可使用XPath
典型使用对比:
python复制# 传统requests方式
import requests
from bs4 import BeautifulSoup
resp = requests.get(url)
soup = BeautifulSoup(resp.text, 'lxml')
title = soup.select_one('h1').text
# requests_html方式
from requests_html import HTMLSession
session = HTMLSession()
resp = session.get(url)
resp.html.render() # 执行JS
title = resp.html.find('h1', first=True).text
2.2 安装与基础配置
推荐使用虚拟环境安装:
bash复制python -m pip install requests-html pyppeteer
注意:首次使用会自动下载Chromium浏览器(约130MB),用于JS渲染。国内用户可能需配置镜像源:
python复制import os os.environ['PYPPETEER_DOWNLOAD_HOST'] = 'https://npm.taobao.org/mirrors'
基础会话配置:
python复制from requests_html import AsyncHTMLSession
async def crawl():
asession = AsyncHTMLSession()
tasks = (asession.get(url) for url in urls)
return await asyncio.gather(*tasks)
3. 实战网页抓取技巧
3.1 动态页面渲染处理
对于Vue/React等动态页面,必须使用render方法:
python复制async def get_dynamic_content():
session = AsyncHTMLSession()
resp = await session.get('https://example.com/single-page-app')
await resp.html.arender(
sleep=2, # 等待页面加载
scrolldown=3, # 模拟滚动触发懒加载
timeout=20
)
items = resp.html.find('.lazy-item')
return [item.text for item in items]
关键参数说明:
wait: 固定等待时间(秒)scrolldown: 滚动次数keep_page: 保持页面不关闭(用于后续操作)
3.2 高级元素定位策略
CSS选择器进阶用法
python复制# 属性选择
resp.html.find('a[href^="https"]') # href以https开头
resp.html.find('div.price:contains("$")') # 包含$符号
# 伪类选择
resp.html.find('tr:nth-child(odd)') # 奇数行
XPath实战示例
python复制# 获取所有包含data-id属性的div
resp.html.xpath('//div[@data-id]')
# 获取第二个ul下的li文本
resp.html.xpath('(//ul)[2]/li/text()')
4. JSON数据处理全攻略
4.1 响应内容智能解析
requests_html会自动检测内容类型:
python复制resp = session.get('https://api.example.com/data.json')
if resp.html.is_json:
data = resp.json() # 自动解析为字典
else:
data = parse_html(resp.html)
4.2 复杂JSON处理技巧
处理嵌套JSON数据时推荐使用jsonpath:
python复制import jsonpath_ng as jp
data = {
"store": {
"books": [
{"title": "Python入门", "price": 59.9},
{"title": "爬虫进阶", "price": 79.9}
]
}
}
expr = jp.parse('$.store.books[?(@.price > 70)]')
[item.value for item in expr.find(data)]
# 输出: [{'title': '爬虫进阶', 'price': 79.9}]
4.3 JSON与HTML混合处理
当遇到JSON数据嵌入HTML的情况:
python复制import re
from json import loads
script = resp.html.find('script#__NEXT_DATA__', first=True).text
json_str = re.search(r'({.*})', script).group(1)
data = loads(json_str)
5. 实战问题排查手册
5.1 常见错误解决方案
| 错误现象 | 可能原因 | 解决方案 |
|---|---|---|
RuntimeError: Cannot find HTML |
页面加载超时 | 增加timeout参数,检查网络 |
Chromium download failed |
下载源不可用 | 配置国内镜像源 |
Element not found |
JS未执行完成 | 调用render()或增加sleep时间 |
JSONDecodeError |
响应非JSON格式 | 先检查resp.html.is_json |
5.2 性能优化技巧
- 连接复用:使用
HTMLSession()保持会话 - 智能渲染:仅对需要JS的页面调用
render() - 并行处理:结合
asyncio实现异步请求
python复制async def fetch_all(urls):
session = AsyncHTMLSession()
tasks = (session.get(url) for url in urls)
return await asyncio.gather(*tasks)
- 缓存机制:对API响应使用
cachetools
python复制from cachetools import TTLCache
cache = TTLCache(maxsize=100, ttl=3600)
def get_with_cache(url):
if url not in cache:
resp = session.get(url)
cache[url] = resp.json()
return cache[url]
6. 安全与反爬策略
6.1 请求头最佳实践
python复制headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
'Accept-Language': 'zh-CN,zh;q=0.9',
'Referer': 'https://www.google.com/'
}
session = HTMLSession()
session.headers.update(headers)
6.2 代理配置方案
python复制proxies = {
'http': 'http://user:pass@proxy.example.com:8080',
'https': 'http://user:pass@proxy.example.com:8080'
}
resp = session.get(url, proxies=proxies, timeout=10)
重要提示:使用代理时务必设置合理的timeout,避免长时间阻塞
7. 数据存储与后续处理
7.1 结构化存储方案
python复制import pandas as pd
from sqlalchemy import create_engine
# 转换为DataFrame
df = pd.DataFrame.from_records(data)
# 存储到SQLite
engine = create_engine('sqlite:///data.db')
df.to_sql('results', engine, if_exists='append', index=False)
7.2 数据清洗技巧
python复制def clean_text(text):
rules = [
(r'\s+', ' '), # 合并空白字符
(r'[\u200b-\u200f]', ''), # 去除零宽字符
(r'[^\w\s-]', '') # 保留字母数字和连接符
]
for pattern, repl in rules:
text = re.sub(pattern, repl, text)
return text.strip()
在实际项目中,我发现合理设置请求间隔(建议0.5-1秒)能显著降低被封风险。对于重要任务,建议实现断点续爬功能,将已爬取的URL列表持久化存储,意外中断后可以从断点恢复。