
本文介绍一种绕过前端 javascript 分页限制的方法:直接提取页面中由 react 注入的初始状态 json 数据,从而获取全部地点信息,避免因动态渲染导致的分页失效问题。
本文介绍一种绕过前端 javascript 分页限制的方法:直接提取页面中由 react 注入的初始状态 json 数据,从而获取全部地点信息,避免因动态渲染导致的分页失效问题。
在使用 requests + BeautifulSoup 爬取类似 https://www.php.cn/link/c391014771f4bf86024b31abdf19483d 这类基于 React(配合 Rails 后端)构建的现代网站时,传统基于 HTML 按钮或链接的分页逻辑往往失效——因为真实分页由前端 JavaScript 控制,且关键数据(如所有地点列表)通常已预先注入页面 DOM 的 <script> 标签中,而非通过多次 AJAX 请求动态加载。</script>
原代码尝试模拟点击“下一页”按钮并查找 链接,但失败的根本原因在于:
- 页面未在 HTML 中静态渲染分页链接;
- Pagination__pageItem__NsQSw 类按钮是 React 动态绑定事件的 DOM 元素,requests 无法执行 .click();
- soup.find('a', href=True) 在当前页面上下文中匹配的是无关导航链接(如顶部菜单),而非真正的分页跳转地址。
✅ 正确解法:定位并解析内嵌的初始化状态 JSON
观察页面源码可发现,React 组件 StateShow 通过 <script class="js-react-on-rails-component" data-component-name="StateShow"> 标签注入了完整的州级地点数据(含 name、fullAddress、url 等字段)。该数据即为全量结果,无需手动翻页。</script>
以下是优化后的完整可运行脚本:
import requests
from bs4 import BeautifulSoup
import csv
import json
base_url = 'https://www.datacenters.com'
main_url = f'{base_url}/locations/united-states/virginia'
def get_detail_data(relative_link, name, address, writer):
link = f'{base_url}{relative_link}'
try:
detail_response = requests.get(link, timeout=10)
detail_response.raise_for_status()
detail_soup = BeautifulSoup(detail_response.content, 'html.parser')
# 提取 Power 和 SQF 字段
power_div = detail_soup.find('div', id='power')
power = power_div.find('strong').text.strip() if power_div and power_div.find('strong') else 'N/A'
sqf_div = detail_soup.find('div', id='statInfo')
sqf = sqf_div.find('strong').text.strip() if sqf_div and sqf_div.find('strong') else 'N/A'
writer.writerow([name, address, power, sqf, link])
print(f"✓ Scraped: {name}")
except Exception as e:
print(f"✗ Failed for {name}: {e}")
# 主爬取流程
with open('datacenters.csv', mode='w', newline='', encoding='utf-8') as file:
writer = csv.writer(file)
writer.writerow(['Name', 'Address', 'Power', 'SQF', 'Link'])
print(f"Fetching initial page: {main_url}")
response = requests.get(main_url, headers={
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'
})
response.raise_for_status()
soup = BeautifulSoup(response.content, 'html.parser')
# 定位 React 初始化数据脚本
state_script = soup.find('script', {
'class': 'js-react-on-rails-component',
'data-component-name': 'StateShow'
})
if not state_script:
raise ValueError("❌ Failed to locate StateShow JSON data in page source.")
try:
state_data = json.loads(state_script.string)
locations = state_data.get('show', {}).get('mapLocations', [])
if not locations:
print("⚠️ No location data found in embedded JSON.")
print(f"✅ Found {len(locations)} locations. Starting detail scraping...")
for idx, loc in enumerate(locations, 1):
# 确保关键字段存在
url = loc.get('url')
name = loc.get('name', 'Unknown')
address = loc.get('fullAddress', 'N/A')
if url:
get_detail_data(url, name, address, writer)
else:
print(f"⚠️ Skipped location #{idx}: missing URL")
except json.JSONDecodeError as e:
raise ValueError(f"❌ Invalid JSON in StateShow script: {e}")
except KeyError as e:
raise ValueError(f"❌ Expected key missing in JSON structure: {e}")
? 关键注意事项:
- 不要依赖可见按钮或分页链接:该站点分页实际由前端调用 /api/v1/locations?page=N&state_id=3173 实现,但全量数据已在首屏 JSON 中提供,直接使用更高效稳定;
- 添加请求头(User-Agent):部分站点会拦截无头请求,模拟浏览器 UA 可降低被拒概率;
- 健壮性处理:对 find('strong') 等嵌套元素做双重判空,避免 AttributeError;
- 异常捕获全覆盖:网络超时、HTTP 错误、JSON 解析失败、字段缺失等均需显式处理;
- 反爬友好提示:若后续遭遇频率限制,建议添加 time.sleep(1) 或使用 requests.Session() 复用连接。
? 总结:面对 SPA(单页应用)类网站,优先检查 <script> 标签中的内嵌 JSON 数据——它往往是比逆向 API 更简单、更可靠的“隐藏数据源”。掌握这一技巧,可大幅提升<a style="color:#f60; text-decoration:underline;" title= "爬虫"href="https://m.php.cn/zt/19377.html" target="_blank">爬虫开发效率与稳定性。</script>










