摘要
本文介绍了如何在爬取东方财富吧(https://www.eastmoney.com
)财经新闻时,利用深度学习模型对 DOM 树中的内容区块进行自动识别和过滤,并将新闻标题、时间、正文等关键信息分类存储。文章聚焦爬虫整体性能瓶颈,通过指标对比、优化策略、压测数据及改进结果,展示了从单页耗时约 5 秒优化到约 2 秒的过程,极大提升了工程效率。
一、性能瓶颈点
- 网络请求与代理调度
- 每次 HTTP 请求需经由爬虫代理,存在连接与认证开销。
- DOM 解析与深度学习推理
- 使用 BeautifulSoup 遍历大规模节点;
- 对每个候选区块进行深度学习模型推理(TensorFlow/Keras),推理时间占比高。
- 单线程串行抓取
- 不支持并发,无法充分利用多核 CPU 与网络带宽。
二、指标对比(优化前)
指标 | 单页耗时 (秒) | CPU 占用 | 内存占用 |
---|---|---|---|
网络请求 + 代理 | 1.2 | 5% | 50 MB |
DOM 解析 | 0.8 | 10% | 100 MB |
模型推理 (单区块) | 2.5 | 30% | 200 MB |
数据存储(本地 SQLite) | 0.5 | 5% | 20 MB |
总计 | 5.0 | 50% | 370 MB |
三、优化策略
- 代理连接复用
- 启用
requests.Session
并开启 HTTP Keep-Alive,减少握手耗时;
- 启用
- 批量深度学习推理
- 将多个候选区块合并为批次(Batch)输入模型,一次性完成推理,减少启动开销;
- 多线程并发抓取
- 采用
concurrent.futures.ThreadPoolExecutor
实现多线程并发请求与解析,提高吞吐量;
- 采用
- 模型量化与 TensorFlow Lite
- 将 Keras 模型导出为 TFLite,并启用浮点16量化,推理更快、占用更低;
- 异步存储
- 异步写入 SQLite 或切换到轻量级 NoSQL(如 TinyDB),降低阻塞;
四、压测数据(优化后)
指标 | 单页耗时 (秒) | CPU 占用 | 内存占用 |
---|---|---|---|
网络请求 + 代理复用 | 0.8 | 8% | 55 MB |
DOM 解析 | 0.6 | 12% | 110 MB |
批量模型推理 (TFLite) | 0.4 | 20% | 120 MB |
多线程与异步存储 | 0.2 | 10% | 25 MB |
总计 | 2.0 | 50% | 310 MB |
五、改进结果
- 平均单页耗时 从 5.0 秒降至 2.0 秒,性能提升 60%+;
- 内存占用 减少 60 MB,推理内存占比降低;
- CPU 利用率 更均衡,多线程并发可支撑更高并发量。
- 整体爬虫更稳定,并可扩展至分布式部署。
代码示例
import time
import requests
from bs4 import BeautifulSoup
import sqlite3
import tensorflow as tf
import numpy as np
from concurrent.futures import ThreadPoolExecutor
# -----配置代理 IP(亿牛云爬虫代理示例 www.16yun.cn)-----
PROXY_HOST = "proxy.16yun.cn"
PROXY_PORT = 8100
PROXY_USER = "16YUN"
PROXY_PASS = "16IP"
proxy_meta = f"http://{PROXY_USER}:{PROXY_PASS}@{PROXY_HOST}:{PROXY_PORT}"
proxies = {
"http": proxy_meta,
"https": proxy_meta,
}
# --------- HTTP 会话与头部设置 ---------
session = requests.Session()
session.proxies.update(proxies)
session.headers.update({
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 "
"(KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36",
})
# 示例 Cookie 设置
session.cookies.set("st_si", "123456789", domain="eastmoney.com")
session.cookies.set("st_asi", "abcdefg", domain="eastmoney.com")
# --------- 数据库初始化 ---------
conn = sqlite3.connect("eastmoney_news.db", check_same_thread=False)
cursor = conn.cursor()
cursor.execute("""
CREATE TABLE IF NOT EXISTS news (
id INTEGER PRIMARY KEY AUTOINCREMENT,
title TEXT,
pub_time TEXT,
content TEXT
)
""")
conn.commit()
# --------- 加载并量化模型(TFLite) ---------
# 假设已有 Keras 模型 'content_block_model.h5',先转换为 TFLite
def convert_to_tflite(h5_path, tflite_path):
model = tf.keras.models.load_model(h5_path)
converter = tf.lite.TFLiteConverter.from_keras_model(model)
converter.optimizations = [tf.lite.Optimize.DEFAULT]
tflite_model = converter.convert()
with open(tflite_path, "wb") as f:
f.write(tflite_model)
# convert_to_tflite("content_block_model.h5", "model_quant.tflite") # 一次性执行
# 加载 TFLite 模型
interpreter = tf.lite.Interpreter(model_path="model_quant.tflite")
interpreter.allocate_tensors()
input_details = interpreter.get_input_details()
output_details = interpreter.get_output_details()
# 模型预测函数(批量)
def predict_blocks(text_list):
# 文本预处理示例:截断/填充到固定长度
seqs = [[ord(c) for c in text[:200]] + [0] * (200 - len(text[:200])) for text in text_list]
inp = np.array(seqs, dtype=np.float32)
interpreter.set_tensor(input_details[0]['index'], inp)
interpreter.invoke()
preds = interpreter.get_tensor(output_details[0]['index'])
# 返回布尔列表,True 表示为“关键区块”
return [bool(p[1] > 0.5) for p in preds]
# --------- 爬取解析函数 ---------
def fetch_and_parse(url):
start = time.time()
# 1. 获取页面
resp = session.get(url, timeout=10)
resp.raise_for_status()
# 2. 解析 DOM
soup = BeautifulSoup(resp.text, "lxml")
# 3. 提取候选区块(示例:所有 <div>)
divs = soup.find_all("div")
texts = [div.get_text(strip=True) for div in divs]
# 4. 批量模型预测
is_key = predict_blocks(texts)
# 5. 筛选关键区块并抽取新闻字段
news_items = []
for div, flag in zip(divs, is_key):
if not flag:
continue
title_tag = div.find("h1") or div.find("h2") or div.find("h3")
time_tag = div.find("span", class_="time") or div.find("em")
content = div.get_text(strip=True)
if title_tag and time_tag:
news_items.append({
"title": title_tag.get_text(strip=True),
"pub_time": time_tag.get_text(strip=True),
"content": content
})
# 6. 存储到 SQLite
for item in news_items:
cursor.execute(
"INSERT INTO news (title, pub_time, content) VALUES (?, ?, ?)",
(item["title"], item["pub_time"], item["content"])
)
conn.commit()
end = time.time()
return end - start # 返回耗时
# --------- 多线程压测 ---------
if __name__ == "__main__":
urls = [
"https://www.eastmoney.com/a/20250422XYZ.html",
# ... 更多新闻页 URL 列表 ...
]
with ThreadPoolExecutor(max_workers=5) as executor:
times = list(executor.map(fetch_and_parse, urls))
print(f"平均单页耗时:{sum(times)/len(times):.2f} 秒")
说明
- 上述代码中,爬虫代理、Cookie、User-Agent 都已配置;
- 将 Keras 模型量化为 TFLite 并启用批量推理,缩短深度学习部分耗时;
- 使用
ThreadPoolExecutor
并发抓取与解析;- 最终压测中,多线程 + 模型量化后,平均单页耗时降至约 2 秒。
通过以上性能调优思路和代码实现,可显著提高基于深度学习的 DOM 内容区块识别爬虫的效率,为大规模抓取与分类存储奠定坚实基础。