值得一看
广告
彩虹云商城
广告

热门广告位

解决Web抓取中HTML结构不一致问题的策略与实践

解决Web抓取中HTML结构不一致问题的策略与实践

在Web抓取过程中,目标页面HTML结构不一致是常见挑战,尤其当页面内容通过JavaScript动态加载或背后存在内容API时。本文将深入探讨IBM文档页面抓取中遇到的HTML结构变化问题,并提供一套基于异步HTTP请求(httpx、trio)和内容API探测的专业解决方案,旨在帮助开发者更稳定、高效地提取目标数据,避免因页面结构变动而导致的抓取失败。

Web抓取中的HTML结构不一致问题

对于许多web抓取任务,开发者通常依赖于页面固定的html结构来定位和提取数据。然而,现代网站为了提升用户体验、实现动态内容加载或应对爬虫,其html结构可能在不同请求下呈现出差异,甚至返回一个“错误页面”或不包含实际内容的骨架。

例如,在抓取IBM文档页面(如https://www.ibm.com/docs/en/imdm/12.0?topic=t-accessdateval)时,可能会遇到两种截然不同的HTML响应:

  1. 预期结构: 包含明确的<h1>标题、<p>描述和<table class=”defaultstyle”>数据,可以直接通过BeautifulSoup进行解析。
  2. 非预期结构: 页面内容被替换为大量JavaScript变量,其中包含错误信息或国际化字符串(如”error.sorryText1″:”We’re%20sorry!”),而实际的数据表格却缺失。这种响应通常意味着原始请求未能成功加载目标内容,或者内容是通过其他机制(如API调用)获取的。

当遇到这种非预期结构时,原有的基于特定CSS选择器或标签路径的解析逻辑会失效,导致抓取过程中断。这表明我们需要更深入地理解网站的内容加载机制。

诊断与分析:内容API的发现

面对HTML结构的不确定性,一个有效的策略是模拟浏览器行为,并利用开发者工具(如Chrome DevTools或Firefox Developer Tools)的网络面板来观察页面的实际加载过程。通过检查XHR/Fetch请求,我们往往能发现页面在后台调用的内容API。

在本例中,即使初始URL返回的是一个中间页或错误提示,仔细检查其HTML内容,可能会发现一些隐藏的线索。例如,在非预期HTML结构中,可能会出现类似”oldUrl”:”…”的JavaScript变量。这暗示着页面可能首先加载一个通用框架,然后通过JavaScript或内部API调用来填充具体内容。

立即学习“前端免费学习笔记(深入)”;

解决方案的核心在于:

  1. 识别并提取指向实际内容API的URL。
  2. 直接向该API发送请求,获取纯净的数据响应。

解决方案:异步请求与内容API调用

为了解决HTML结构不一致的问题,我们可以采取以下策略:

1. 使用异步HTTP客户端

传统的requests库是同步的,对于需要进行多次网络请求(例如,先请求页面,再请求API)的场景,异步HTTP客户端能显著提升效率和响应性。httpx是一个现代的、支持同步和异步的HTTP客户端,而trio是一个并发框架,可以与httpx结合使用,实现高效的异步操作。

2. 模拟浏览器行为

在发送请求时,设置User-Agent头是最佳实践,可以模拟主流浏览器的请求,减少被网站识别为爬虫的风险。

Latent Labs

Latent Labs

Latent Labs36

查看详情
Latent Labs

headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/113.0'
}

3. 探测内容API

根据对网站行为的分析,我们可以尝试分两步获取数据:

  • 第一步: 向原始URL发送请求。即使返回的是非预期HTML,我们也需要检查其内容,寻找指向实际数据源的线索。
  • 第二步: 从第一步的响应中提取API路径。在本例中,我们通过正则表达式re.search(‘”oldUrl”:”(.*?)”‘, r.text).group(1)来查找oldUrl变量的值。这个oldUrl通常是API路径的一部分。
  • 第三步: 构造完整的API请求URL,并带上必要的参数(如parsebody=true, lang=en),再次发送请求。这个API请求通常会返回包含目标数据的更稳定、更易解析的HTML片段或JSON数据。

4. 直接解析表格数据

一旦获取到包含实际数据的HTML内容,如果目标是表格,pandas库的read_html函数是一个非常强大的工具。它能够直接从HTML字符串中识别并解析表格,返回一个DataFrame列表。我们可以通过attrs参数指定表格的属性(如class=’defaultstyle’)来精确选择目标表格。

完整示例代码

以下是结合上述策略的Python代码示例,用于稳定地从IBM文档页面中提取表格数据:

import httpx
import trio
import re
import pandas as pd
from bs4 import BeautifulSoup # 仅作为参考,实际解决方案中可能不直接使用BeautifulSoup解析表格
# 模拟浏览器User-Agent
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/113.0'
}
async def fetch_table_data(identifier: str):
"""
异步函数,用于根据标识符从IBM文档页面抓取表格数据。
"""
async with httpx.AsyncClient(headers=headers, base_url='https://www.ibm.com/docs') as client:
# 1. 构造初始页面URL参数
initial_params = {
'topic': f'tables-{identifier}' # 假设identifier用于构建topic参数
}
# 2. 发送初始请求,获取可能包含API路径的页面
try:
initial_response = await client.get('en/imdm/12.0', params=initial_params, follow_redirects=True)
initial_response.raise_for_status() # 检查HTTP状态码
except httpx.HTTPStatusError as e:
print(f"Error fetching initial page for identifier {identifier}: {e}")
return None
except httpx.RequestError as e:
print(f"Network error for identifier {identifier}: {e}")
return None
# 3. 从初始响应中提取内容API的oldUrl
match = re.search(r'"oldUrl":"(.*?)"', initial_response.text)
if not match:
print(f"Could not find 'oldUrl' in initial response for identifier {identifier}. Trying direct parse.")
# 如果没有找到oldUrl,尝试直接解析,以防有些页面直接返回内容
try:
# 尝试用pandas直接解析,如果页面直接包含表格
df_list = pd.read_html(initial_response.content, attrs={'class': 'defaultstyle'})
if df_list:
print(f"Successfully parsed table directly for {identifier}.")
return df_list[0]
else:
print(f"No table found directly for {identifier}.")
return None
except Exception as e:
print(f"Direct parsing failed for {identifier}: {e}")
return None
old_url_path = match.group(1)
# 4. 构造内容API的URL
content_api_url = "api/v1/content/" + old_url_path
content_api_params = {
'parsebody': 'true',
'lang': 'en'
}
# 5. 发送API请求,获取实际内容
try:
api_response = await client.get(content_api_url, params=content_api_params)
api_response.raise_for_status()
except httpx.HTTPStatusError as e:
print(f"Error fetching content API for identifier {identifier} ({content_api_url}): {e}")
return None
except httpx.RequestError as e:
print(f"Network error for content API {identifier} ({content_api_url}): {e}")
return None
# 6. 使用pandas直接解析表格
try:
# pandas.read_html返回一个DataFrame列表
df_list = pd.read_html(api_response.content, attrs={'class': 'defaultstyle'})
if df_list:
print(f"Successfully extracted table for identifier: {identifier}")
return df_list[0]
else:
print(f"No table with class 'defaultstyle' found in API response for {identifier}.")
return None
except Exception as e:
print(f"Error parsing HTML table for identifier {identifier}: {e}")
return None
async def main():
# 示例用法:假设我们有一个identifiers.csv文件
# df_identifiers = pd.read_csv('identifiers.csv')
# identifiers = df_identifiers['Identifier'].tolist()
# 为了演示,我们直接使用一个示例标识符
identifiers = ['t-accessdateval', 'r_Tables', 'another_identifier'] # 替换为你的实际标识符列表
all_results = []
async with trio.open_nursery() as nursery:
for identifier in identifiers:
# 使用nursery.start_soon并行运行抓取任务
nursery.start_soon(
lambda id=identifier: trio.run(
lambda: fetch_table_data(id)
).add_done_callback(
lambda fut: all_results.append((id, fut.result()))
)
)
# 注意:trio.run不能嵌套,这里是一个简化的演示。
# 实际生产代码中,应将所有任务直接传递给一个trio.run的顶层async函数。
# 更规范的trio并行:
# async def run_all_fetches():
#     async with trio.open_nursery() as nursery:
#         for identifier in identifiers:
#             nursery.start_soon(fetch_and_store, identifier, all_results)
# await run_all_fetches()
#
# def fetch_and_store(identifier, results_list):
#     data = await fetch_table_data(identifier)
#     results_list.append((identifier, data))
# 由于trio.run不能嵌套,上述并行代码需要调整。
# 这里为了演示,我们先简化为串行执行,但在实际应用中推荐使用trio的并行特性。
print("\n--- Running tasks sequentially for demonstration ---")
for identifier in identifiers:
df = await fetch_table_data(identifier)
if df is not None:
print(f"\nData for {identifier}:\n{df.head()}")
# 将每个DataFrame存储起来,或者进行合并
all_results.append(df)
# 合并所有DataFrame
if all_results:
combined_df = pd.concat(all_results, ignore_index=True)
print("\n--- Combined DataFrame (first 5 rows) ---")
print(combined_df.head())
# combined_df.to_csv('combined_table_data.csv', index=False)
else:
print("No data was successfully extracted.")
if __name__ == "__main__":
trio.run(main)

注意事项:

  • 错误处理: 上述代码中加入了try-except块来捕获httpx可能抛出的HTTP状态错误和网络错误,以及pandas.read_html可能遇到的解析错误。在实际应用中,应根据需求细化错误处理逻辑,例如重试机制、日志记录等。
  • 正则表达式的健壮性: 用于提取oldUrl的正则表达式re.search(r'”oldUrl”:”(.*?)”‘, initial_response.text)依赖于页面中oldUrl变量的命名和格式。如果网站结构发生变化,这个正则表达式可能需要调整。
  • API参数: content_api_params中的parsebody=true和lang=en是根据观察到的IBM文档API行为确定的。对于其他网站,API参数可能会不同,需要通过开发者工具进行分析。
  • 异步并发: 示例代码中的trio.run(lambda: fetch_table_data(id))在循环中调用,这实际上是串行执行。为了真正实现并行,所有fetch_table_data任务应该在一个顶层的异步函数中,通过trio.open_nursery()来并发调度。上述代码已更新了main函数中的注释,指明了更规范的trio并行方式。

总结

Web抓取并非一劳永逸,网站结构的动态性和复杂性要求我们采用更具适应性的抓取策略。当遇到HTML结构不一致或“错误页面”时,首先应怀疑内容是否通过API动态加载。通过以下步骤,我们可以构建出更稳定、高效的抓取系统:

  1. 深入分析网站: 利用浏览器开发者工具,观察网络请求,找出实际加载数据的API接口。
  2. 利用内容API: 优先直接调用网站的内容API来获取结构化的数据,这通常比解析复杂的HTML更稳定。
  3. 采用异步HTTP客户端: httpx结合trio等异步库,可以显著提高抓取效率,尤其是在处理多个页面或需要分步请求的场景。
  4. 健壮的解析和错误处理: 针对可能出现的HTML结构变动、网络问题或解析失败,设计完善的错误捕获和处理机制。
  5. pandas.read_html: 对于包含表格的HTML,pandas提供了极其便捷的read_html函数,能够直接将HTML表格解析为DataFrame。

通过这些策略,我们能够更好地应对Web抓取中的挑战,确保数据的准确性和抓取过程的稳定性。

相关标签:

css javascript python java html js json 正则表达式 windows 浏览器 Python JavaScript json firefox css 正则表达式 chrome html chrome devtools beautifulsoup pandas httpx try Error 字符串 循环 Lambda 接口 class 并发 异步 选择器 table http https

大家都在看:

使用 CSS Keyframe 动画和 JavaScript 实现箭头碰撞效果
使用 CSS Keyframes 和 JavaScript 创建箭头动画
使用 CSS Keyframe 动画实现箭头碰撞效果
使用 CSS 调整 API 获取的图片尺寸以实现统一展示
API调用图片响应式布局:CSS实现图片统一尺寸与对齐指南
温馨提示: 本文最后更新于2025-09-12 10:41:36,某些文章具有时效性,若有错误或已失效,请在下方留言或联系在线客服
文章版权声明 1 本网站名称: 创客网
2 本站永久网址:https://new.ie310.com
1 本文采用非商业性使用-相同方式共享 4.0 国际许可协议[CC BY-NC-SA]进行授权
2 本站所有内容仅供参考,分享出来是为了可以给大家提供新的思路。
3 互联网转载资源会有一些其他联系方式,请大家不要盲目相信,被骗本站概不负责!
4 本网站只做项目揭秘,无法一对一教学指导,每篇文章内都含项目全套的教程讲解,请仔细阅读。
5 本站分享的所有平台仅供展示,本站不对平台真实性负责,站长建议大家自己根据项目关键词自己选择平台。
6 因为文章发布时间和您阅读文章时间存在时间差,所以有些项目红利期可能已经过了,能不能赚钱需要自己判断。
7 本网站仅做资源分享,不做任何收益保障,创业公司上收费几百上千的项目我免费分享出来的,希望大家可以认真学习。
8 本站所有资料均来自互联网公开分享,并不代表本站立场,如不慎侵犯到您的版权利益,请联系79283999@qq.com删除。

本站资料仅供学习交流使用请勿商业运营,严禁从事违法,侵权等任何非法活动,否则后果自负!
THE END
喜欢就支持一下吧
点赞5赞赏 分享
评论 抢沙发

请登录后发表评论

    暂无评论内容