1. 为什么选择requests_html与json这对黄金组合
在Python爬虫领域,requests_html库正逐渐成为新一代的轻量级抓取利器。与传统requests+BeautifulSoup组合相比,它最大的特点是内置了完整的HTML解析和JavaScript渲染能力。我在实际项目中验证过,对于90%的现代网页抓取需求,这个组合能提供开箱即用的解决方案。
requests_html的核心优势在于:
- 自动处理动态渲染:通过集成Pyppeteer(Chromium的无头浏览器),能执行页面中的JavaScript代码
- 类jQuery的选择器:支持CSS选择器和XPath双模式定位元素
- 会话保持:自动管理cookies和headers,减少反爬困扰
- 智能编码检测:自动处理网页编码问题,避免乱码
而json作为数据交换的事实标准,在爬虫中扮演着关键角色。我常遇到的情况是:
- 直接解析API返回的JSON数据
- 将抓取的HTML内容结构化后转为JSON存储
- 使用JSON作为中间格式进行数据清洗
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 环境搭建与基础配置
2.1 安装注意事项
安装requests_html时常见的坑是依赖冲突。推荐使用虚拟环境:
bash复制python -m venv scrape_env
source scrape_env/bin/activate # Linux/Mac
scrape_env\Scripts\activate # Windows
pip install requests-html pyppeteer
重要提示:首次运行会自动下载Chromium(约200MB),国内用户建议配置镜像源:
python复制import os os.environ['PYPPETEER_DOWNLOAD_HOST'] = 'https://npm.taobao.org/mirrors'
2.2 基础请求模板
这是我经过多个项目验证的稳健请求模板:
python复制from requests_html import HTMLSession
session = HTMLSession(browser_args=["--no-sandbox", "--disable-setuid-sandbox"])
def safe_get(url, retry=3):
for i in range(retry):
try:
r = session.get(url, timeout=10)
r.raise_for_status()
if len(r.content) > 500: # 过滤无效响应
return r
except Exception as e:
print(f"Attempt {i+1} failed: {str(e)}")
if i == retry - 1:
raise
time.sleep(2**i) # 指数退避
关键参数说明:
browser_args:禁用沙盒模式可解决部分Linux系统的权限问题- 指数退避策略:2, 4, 8秒的间隔重试,符合爬虫伦理
- 内容长度检查:避免返回空白页或验证码页面
3. 实战解析:从HTML到结构化JSON
3.1 动态页面渲染技巧
处理SPA(单页应用)时,必须使用render方法:
python复制r = session.get('https://dynamic-site.com')
r.html.render(sleep=2, keep_page=True, scrolldown=3)
参数优化建议:
sleep:根据网络状况调整,2秒适用于大部分场景scrolldown:模拟滚动触发懒加载,数字代表滚动次数keep_page:保持页面对象用于后续操作
3.2 数据提取四步法
这是我总结的高效提取流程:
- 定位容器:先用Chrome开发者工具观察整体结构
python复制items = r.html.find('.product-list > li')
- 多模式选择:CSS选择器和XPath混合使用
python复制name = item.find('h3', first=True).text
price = item.xpath('//span[@class="price"]/text()')[0]
- 属性提取:注意相对路径和绝对路径的区别
python复制image_url = item.absolute_links.pop() # 转换相对链接
- 数据清洗:使用正则处理特殊字符
python复制import re
clean_price = re.sub(r'[^\d.]', '', price)
3.3 转为JSON的最佳实践
结构化转换时推荐使用Python标准库的json模块:
python复制import json
data = []
for item in items:
data.append({
"name": name,
"price": float(clean_price),
"image": image_url,
"timestamp": datetime.now().isoformat()
})
with open('output.json', 'w', encoding='utf-8') as f:
json.dump(data, f, ensure_ascii=False, indent=2)
注意事项:
ensure_ascii=False:保证中文正常显示- 添加时间戳:便于后续增量爬取
- 类型转换:提前处理数字、日期等特殊格式
4. 高级技巧与反反爬策略
4.1 请求头优化方案
这是我收集的现代浏览器指纹头:
python复制headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36",
"Accept-Language": "zh-CN,zh;q=0.9,en;q=0.8",
"Accept-Encoding": "gzip, deflate, br",
"Referer": "https://www.google.com/",
"Sec-Ch-Ua": '"Google Chrome";v="91", "Chromium";v="91", ";Not A Brand";v="99"'
}
4.2 智能延迟系统
根据响应状态动态调整请求频率:
python复制class SmartThrottle:
def __init__(self, base_delay=1.0):
self.base = base_delay
self.factor = 1
def adjust(self, response):
if response.status_code == 429:
self.factor *= 2
elif self.factor > 1 and random.random() > 0.7:
self.factor *= 0.9
def wait(self):
time.sleep(self.base * self.factor + random.uniform(0, 0.5))
4.3 验证码处理方案
遇到验证码时的应急方案:
- 自动识别简单验证码:
python复制from pytesseract import image_to_string
captcha_img = r.html.find('#captcha', first=True)
if captcha_img:
text = image_to_string(captcha_img)
- 人工干预模式:
python复制if "验证码" in r.text:
input("请在浏览器中手动处理验证码后按回车继续:")
r = session.get(r.url) # 复用当前会话
5. 数据存储与管道设计
5.1 增量爬取架构
使用MD5校验实现增量存储:
python复制import hashlib
def get_content_hash(content):
return hashlib.md5(content.encode('utf-8')).hexdigest()
seen = set()
for item in data:
item_hash = get_content_hash(json.dumps(item))
if item_hash not in seen:
seen.add(item_hash)
# 存储逻辑...
5.2 多格式输出管道
扩展存储选项:
python复制def save_data(data, format='json'):
if format == 'json':
with open('data.json', 'a') as f:
json.dump(data, f)
elif format == 'csv':
pd.DataFrame(data).to_csv('data.csv', mode='a')
elif format == 'mongodb':
client = pymongo.MongoClient()
db = client['scrape_db']
db.items.insert_many(data)
5.3 错误处理机制
健壮的生产级处理方案:
python复制from tenacity import retry, stop_after_attempt, wait_exponential
@retry(stop=stop_after_attempt(3), wait=wait_exponential(multiplier=1))
def process_item(item):
try:
# 处理逻辑...
return result
except Exception as e:
logger.error(f"处理失败: {str(e)}")
raise
6. 性能优化实战
6.1 异步请求模式
利用asyncio提升吞吐量:
python复制async def async_fetch(url):
async with HTMLSession() as session:
r = await session.get(url)
await r.html.arender()
return r
async def main(urls):
tasks = [async_fetch(url) for url in urls]
return await asyncio.gather(*tasks)
6.2 内存优化技巧
处理大页面时的内存管理:
python复制with HTMLSession() as session:
r = session.get(url, stream=True)
for chunk in r.iter_content(chunk_size=1024):
process(chunk) # 流式处理
r.close() # 显式释放资源
6.3 分布式扩展思路
使用Redis队列实现任务分发:
python复制import redis
from rq import Queue
conn = redis.Redis()
q = Queue(connection=conn)
def worker_task(url):
# 爬取逻辑...
return data
job = q.enqueue(worker_task, url)
7. 真实项目经验分享
在最近一个电商价格监控项目中,我发现几个关键点:
- 元素定位陷阱:某些网站会随机生成class名称,此时应该:
python复制# 改用稳定的结构定位
price = item.find('[itemprop="price"]', first=True).attrs['content']
- JSON直取技巧:很多网站会将数据藏在
