别再用正则硬啃JS数据了!手把手教你用Python requests+json模块搞定金十快讯爬虫(附完整代码)
·
别再用正则硬啃JS数据了!Python requests+json模块实战解析非标准API响应
当你用Python的requests库抓取网页时,是否经常遇到这样的尴尬场景:明明浏览器开发者工具里能看到清晰的JSON数据,但实际获取到的却是包裹在JavaScript变量中的"伪JSON"?这种数据既不是标准JSON格式,也不是常规HTML,用正则表达式处理既繁琐又脆弱。今天我们就来彻底解决这个痛点,用更优雅的方式处理这类混合数据。
1. 为什么JavaScript格式数据如此常见?
现代网页大量依赖前端渲染,数据通常以JavaScript变量形式嵌入HTML。以金融数据网站为例,实时行情、快讯等内容需要频繁更新,前端通过JavaScript动态加载是最佳实践。这种模式带来了几个典型特征:
- 数据包裹在
var data = {...}或window.data = {...}这样的赋值语句中 - 末尾常带有分号作为语句结束符
- 键名可能不加引号(虽然这在标准JSON中不合法,但JavaScript允许)
- 可能包含注释、函数调用等非数据内容
# 典型的混合数据示例
js_data = """
var flashData = {
// 更新时间:2023-07-15
news: [
{time: "09:30", content: "美联储宣布维持利率不变"},
{time: "10:15", content: "原油价格突破80美元关口"}
],
updatedAt: new Date().getTime()
};
"""
2. 从正则表达式到结构化处理的思维转变
很多开发者的第一反应是写正则表达式提取大括号内的内容,比如:
import re
pattern = r"var flashData = ({.*?});"
match = re.search(pattern, js_data, re.DOTALL)
if match:
json_str = match.group(1)
这种方法虽然能工作,但存在明显缺陷:
- 脆弱性:对数据格式变化极其敏感,比如换行、空格、注释都会导致匹配失败
- 维护成本:随着数据结构复杂化,正则表达式会变得难以理解和调试
- 局限性:无法处理嵌套结构或特殊字符
更专业的做法是将问题分解为两个步骤:
- 定位数据边界:找到JSON部分的起始和结束位置
- 标准化处理:将非标准JSON转换为标准格式
3. 实战:处理金十快讯的JavaScript数据
让我们通过金十快讯的实际案例,演示如何系统性地解决这个问题。完整流程如下:
3.1 获取原始数据
首先使用requests获取网页内容:
import requests
url = "https://www.jin10.com/flash_newest.js"
headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"
}
response = requests.get(url, headers=headers)
raw_data = response.text
3.2 数据预处理关键步骤
获得的数据可能如下形式:
var flash_data = {
"news": [
{"time": "09:30", "content": "美联储利率决议即将公布"},
{"time": "10:15", "content": "欧洲央行维持宽松政策"}
]
};
处理流程:
def clean_js_data(js_string):
# 步骤1:找到第一个{和最后一个}的位置
start = js_string.find('{')
end = js_string.rfind('}') + 1
# 步骤2:提取JSON部分
json_str = js_string[start:end]
# 步骤3:修复常见非标准格式
json_str = json_str.replace("'", '"') # 单引号转双引号
json_str = json_str.replace("True", "true") # Python布尔值转JSON标准
json_str = json_str.replace("False", "false")
return json_str
3.3 完整解析代码
将上述步骤整合为可复用的工具函数:
import json
from typing import Any
def parse_js_object(js_string: str) -> Any:
"""解析JavaScript对象为Python数据结构
Args:
js_string: 包含JavaScript对象的字符串,如 'var data = {...};'
Returns:
解析后的Python对象(dict/list等)
Raises:
ValueError: 当无法找到有效JSON对象或解析失败时
"""
# 定位对象边界
start = js_string.find('{')
end = js_string.rfind('}') + 1
if start == -1 or end == 0:
raise ValueError("No JavaScript object found in the string")
# 提取并清理JSON字符串
json_str = js_string[start:end]
json_str = json_str.replace("'", '"')
try:
return json.loads(json_str)
except json.JSONDecodeError as e:
# 尝试修复常见的非标准JSON问题
json_str = fix_common_json_issues(json_str)
return json.loads(json_str)
def fix_common_json_issues(json_str: str) -> str:
"""修复常见的非标准JSON问题"""
# 处理未加引号的键名
json_str = re.sub(r'([{,]\s*)(\w+)(\s*:)', r'\1"\2"\3', json_str)
# 处理JavaScript的undefined/null
json_str = json_str.replace("undefined", "null")
return json_str
4. 高级技巧与边界情况处理
实际项目中,你可能会遇到更复杂的情况。以下是几种常见场景的解决方案:
4.1 处理多级嵌套的JavaScript对象
当数据包含多层嵌套时,简单的字符串处理可能不够:
var config = {
api: {
endpoint: "https://api.example.com",
timeout: 5000
},
features: {
analytics: true,
logging: {
level: "debug",
format: "json"
}
}
};
解决方案是使用更精确的定位方法:
def extract_nested_js_object(js_string, variable_name):
"""提取特定变量名的JavaScript对象"""
pattern = rf"var {variable_name} = ({{.*?}});"
match = re.search(pattern, js_string, re.DOTALL)
if not match:
raise ValueError(f"Variable {variable_name} not found")
return parse_js_object(match.group(1))
4.2 性能优化:处理大型数据集
当处理MB级别的JavaScript数据时,内存效率变得重要:
def parse_large_js_file(file_path):
"""流式处理大型JavaScript数据文件"""
with open(file_path, 'r', encoding='utf-8') as f:
buffer = ""
in_object = False
brace_count = 0
for line in f:
if not in_object:
if '{' in line:
start = line.find('{')
buffer = line[start:]
in_object = True
brace_count = 1
continue
brace_count += line.count('{')
brace_count -= line.count('}')
buffer += line
if brace_count == 0:
yield parse_js_object(buffer)
in_object = False
buffer = ""
4.3 安全考虑:防范恶意数据
处理外部数据时,安全至关重要:
def safe_parse_js_object(js_string, max_size=10*1024*1024):
"""带安全检查的JavaScript对象解析"""
if len(js_string) > max_size:
raise ValueError("Input size exceeds maximum allowed")
# 检查潜在的危险模式
if "eval(" in js_string or "Function(" in js_string:
raise ValueError("Dangerous JavaScript patterns detected")
return parse_js_object(js_string)
5. 工程化实践:构建可维护的爬虫系统
将上述技术整合到完整项目中时,建议采用以下架构:
financial_news_crawler/
├── crawlers/ # 爬虫实现
│ ├── base.py # 基础爬虫类
│ └── jin10.py # 金十快讯专用爬虫
├── parsers/ # 数据解析器
│ └── js_parser.py # JavaScript数据解析工具
├── storage/ # 存储后端
│ ├── json_file.py # JSON文件存储
│ └── database.py # 数据库存储
└── pipelines/ # 数据处理流水线
└── news.py # 新闻处理流水线
示例金十快讯爬虫实现:
from datetime import datetime
from typing import List, Dict
from .base import BaseCrawler
from ..parsers.js_parser import parse_js_object
class Jin10FlashNewsCrawler(BaseCrawler):
"""金十快讯爬虫"""
def __init__(self):
self.api_url = "https://www.jin10.com/flash_newest.js"
self.headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64)"
}
def fetch(self) -> List[Dict]:
"""获取并解析快讯数据"""
response = self._request(self.api_url)
js_data = response.text
try:
data = parse_js_object(js_data)
return self._transform(data.get('news', []))
except ValueError as e:
self.logger.error(f"Failed to parse JS data: {e}")
return []
def _transform(self, raw_news: List) -> List[Dict]:
"""数据清洗转换"""
return [{
"time": self._parse_time(item['time']),
"content": item['content'].strip(),
"source": "jin10",
"crawled_at": datetime.now()
} for item in raw_news]
def _parse_time(self, time_str: str) -> datetime:
"""解析时间字符串"""
today = datetime.now().date()
return datetime.strptime(f"{today} {time_str}", "%Y-%m-%d %H:%M")
这种架构的优势在于:
- 关注点分离:解析逻辑与爬虫实现解耦
- 可扩展性:轻松添加新的数据源或存储后端
- 可维护性:每个组件职责单一,易于测试和修改
- 复用性:JavaScript解析器可用于其他项目
更多推荐


所有评论(0)