
本教程旨在解决使用BeautifulSoup直接解析HTML元素时,无法获取到通过JavaScript动态加载内容的常见问题。我们将深入探讨当目标文本被嵌入到标签内的JavaScript变量(如window.__INITIAL_STATE__)中时,如何结合使用requests库、正则表达式和json模块来准确提取所需数据,并最终利用BeautifulSoup对提取出的HTML片段进行清洗,从而实现更高效、更精准的网页内容抓取。
传统HTML解析的局限性
在进行网页数据抓取时,我们经常会遇到内容无法通过简单的html标签选择器获取的情况。例如,当尝试使用beautifulsoup的find_all(‘p’)方法来提取段落文本时,可能会发现返回的文本为空,或者只获取到不相关的部分,甚至出现nameerror: name ‘text’ is not defined这样的错误。这通常是因为目标内容并非直接存在于页面的静态html结构中,而是通过javascript在页面加载后动态注入的。
以提供的案例为例,尽管页面HTML中存在
标签,但我们真正想要的文章标题和摘要文本并未直接包含在这些可见的
标签内。相反,它们被封装在一个名为window.__INITIAL_STATE__的JavaScript变量中,以JSON格式存储在页面的标签内部。
原始尝试代码示例:
import requests from bs4 import BeautifulSoupURL = "https://habr.com/ru/hubs/gamedev/articles/"page = requests.get(URL).contentsoup = BeautifulSoup(page, "html.parser")post = soup.find("article", class_="tm-articles-list__item")# 这里的discription可能为空或不包含所需内容discription = post.find_all('p')for post_text in discription: text = post_text.get_text()# 如果discription为空,text变量将不会被定义,导致NameErrorprint(text)
这种方法在面对动态加载内容时会失效,因为BeautifulSoup只能解析requests.get()获取到的原始HTML文本,而不能执行JavaScript来渲染页面。
识别动态加载内容
要判断目标内容是否为动态加载,可以通过以下步骤:
立即学习“Java免费学习笔记(深入)”;
- 查看页面源代码: 在浏览器中右键点击页面,选择“查看页面源代码”(或“View Page Source”)。搜索你想要抓取的内容,如果找不到,或者只找到一些占位符,那么很可能就是动态加载的。
- 检查开发者工具的网络请求: 打开浏览器的开发者工具(F12),切换到“网络”(Network)选项卡。刷新页面,观察是否有XHR(XMLHttpRequest)或Fetch请求加载了包含目标数据的新数据。
- 检查标签: 在页面源代码中搜索标签,尤其是那些看起来包含大量JSON数据或定义了全局JavaScript变量的脚本块。例如,window.__INITIAL_STATE__就是一个常见的模式,许多现代前端框架(如React、Vue等)会用它来初始化页面状态。
在我们的案例中,通过检查页面源代码,可以发现文章的标题和摘要信息被封装在window.__INITIAL_STATE__这个JavaScript对象中。
解决方案:正则表达式与JSON解析
当内容被嵌入到JavaScript变量中时,我们需要采用一种混合策略:
- 使用requests获取页面的原始HTML文本。
- 使用正则表达式从HTML文本中提取包含目标数据的JavaScript变量内容。
- 将提取出的JavaScript变量内容解析为JSON对象。
- 在JSON对象中导航,找到并提取所需的数据。
- 如果提取出的数据仍包含HTML标签,可再次使用BeautifulSoup进行清洗。
步骤一:获取页面原始文本
首先,我们需要获取完整的页面文本,而不仅仅是BeautifulSoup解析后的DOM结构。requests.get(URL).text可以获取到包含JavaScript代码的原始字符串。
步骤二:使用正则表达式提取JavaScript变量
为了从原始文本中精确地提取window.__INITIAL_STATE__变量的值,我们可以使用正则表达式。观察目标变量的结构,它通常以window.__INITIAL_STATE__=开头,并以特定的字符序列(如}}后跟分号)结束。
import re# ... (其他导入)page = requests.get(URL).text# 使用正则表达式匹配并提取window.__INITIAL_STATE__变量的内容# 注意:(.*)}}; 捕获了从等号后到第一个}};之间的所有内容data_match = re.search(r"window.__INITIAL_STATE__=(.*}});", page)if data_match: data_str = data_match.group(1)else: print("未找到 window.__INITIAL_STATE__ 数据。") exit()这里的正则表达式r”window.__INITIAL_STATE__=(.*}});”的含义是:
- window.__INITIAL_STATE__=:匹配字面字符串window.__INITIAL_STATE__=。_是特殊字符,需要转义。
- (.*):这是一个捕获组,.*匹配任意字符(除了换行符)零次或多次。它将捕获=之后的所有内容。
- }});:匹配字面字符串}});,作为变量内容的结束标志。
步骤三:解析JSON数据
提取到的data_str是一个JSON格式的字符串,我们需要使用json模块将其转换为Python字典或列表。
import json# ... (其他代码)data = json.loads(data_str)
步骤四:导航至目标数据
一旦数据被解析为Python对象,我们就可以像操作普通字典和列表一样,通过键和索引来访问所需的信息。这通常需要对目标网站的JSON结构有所了解,可以通过浏览器开发者工具(Network -> XHR/Fetch -> Response 或 Console -> window.__INITIAL_STATE__)进行探索。
在提供的案例中,文章数据位于data[“articlesList”][“articlesList”]中。我们可以遍历这个字典,并根据时间戳进行排序,以获取最新的文章。
# ... (其他代码)for article_id, article_data in sorted( data["articlesList"]["articlesList"].items(), key=lambda item: item[1]["timePublished"], # 根据发布时间排序 reverse=True, # 倒序,获取最新文章): # 提取文章标题 title = article_data["titleHtml"] # 提取文章摘要,可能包含HTML标签 lead_text_html = article_data["leadData"]["textHtml"] # 我们只取第一篇文章作为示例 break
步骤五:处理HTML片段
从JSON中提取出的lead_text_html可能仍然包含HTML标签(如、等)。为了获取纯文本,我们可以再次利用BeautifulSoup。
# ... (其他代码)# 使用BeautifulSoup清洗摘要中的HTML标签clean_lead_text = BeautifulSoup(lead_text_html, "html.parser").textprint(title)print(clean_lead_text)
完整示例代码
结合上述步骤,完整的解决方案代码如下:
import reimport jsonimport requestsfrom bs4 import BeautifulSoupURL = "https://habr.com/ru/hubs/gamedev/articles/" # 目标网站URL# 1. 获取页面原始文本page = requests.get(URL).text# 2. 使用正则表达式从页面文本中提取JavaScript变量__INITIAL_STATE__的内容# 匹配从"window.__INITIAL_STATE__="开始,到第一个"}};"结束的JSON字符串data_match = re.search(r"window.__INITIAL_STATE__=(.*}});", page)if data_match: data_str = data_match.group(1)else: print("错误:未能在页面中找到 'window.__INITIAL_STATE__' 数据。") exit()# 3. 解析提取到的JSON字符串data = json.loads(data_str)# 4. 导航至目标数据并提取信息# 遍历文章列表,并按发布时间倒序排序,以获取最新文章for article_id, article_data in sorted( data["articlesList"]["articlesList"].items(), key=lambda item: item[1]["timePublished"], reverse=True,): # 提取文章标题(通常是HTML片段) title_html = article_data["titleHtml"] # 提取文章摘要(通常是HTML片段) lead_data_html = article_data["leadData"]["textHtml"] # 5. 使用BeautifulSoup清洗HTML片段,获取纯文本 clean_title = BeautifulSoup(title_html, "html.parser").text clean_lead_text = BeautifulSoup(lead_data_html, "html.parser").text print(f"标题: {clean_title}") print(f"摘要: {clean_lead_text}n") # 示例中我们只获取第一篇文章,然后退出循环 break输出结果示例:
标题: 30 лет DOOM: новый код — новые баги摘要: Сегодня первой игре из серии DOOM исполняется ровно 30 лет! Мы не могли обойти стороной это событие и в честь этого решили посмотреть, как же выглядит код этой легендарной игры спустя годы.
注意事项
- 页面结构变化: 网站的HTML结构或JavaScript变量名/JSON结构可能会随时间变化。如果网站更新,您的正则表达式或JSON路径可能需要调整。
- 正则表达式的鲁棒性: 正则表达式需要足够精确以避免误匹配,但也要足够灵活以应对微小的格式差异。过于严格的正则可能因细微变化而失效。
- JSON结构探索: 在编写代码之前,花时间在浏览器开发者工具中仔细检查目标JSON数据的结构是至关重要的。
- 错误处理: 在实际项目中,应加入更完善的错误处理机制,例如使用try-except块来处理requests请求失败、正则表达式匹配失败或JSON解析错误等情况。
- 性能考量: 对于大规模抓取,频繁地使用正则表达式和JSON解析可能会有性能开销。考虑缓存或优化数据处理逻辑。
总结
当传统的HTML解析工具BeautifulSoup无法直接获取网页内容时,这通常意味着内容是通过JavaScript动态加载的。通过结合requests获取原始页面文本,使用re模块的正则表达式提取嵌入在标签中的JavaScript变量(尤其是JSON格式的数据),再通过json模块进行解析,我们能够有效地获取这些动态内容。最后,BeautifulSoup仍然可以用于清洗从JSON中提取出的HTML片段,确保获得纯净的文本数据。这种多工具组合的方法是处理现代复杂网页抓取任务的关键技能。
以上就是网页内容抓取进阶:解析JavaScript动态加载的数据的详细内容,更多请关注创想鸟其它相关文章!
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 chuangxiangniao@163.com 举报,一经查实,本站将立刻删除。
发布者:程序猿,转转请注明出处:https://www.chuangxiangniao.com/p/1375763.html - 正则表达式的鲁棒性: 正则表达式需要足够精确以避免误匹配,但也要足够灵活以应对微小的格式差异。过于严格的正则可能因细微变化而失效。
- 页面结构变化: 网站的HTML结构或JavaScript变量名/JSON结构可能会随时间变化。如果网站更新,您的正则表达式或JSON路径可能需要调整。
- 检查开发者工具的网络请求: 打开浏览器的开发者工具(F12),切换到“网络”(Network)选项卡。刷新页面,观察是否有XHR(XMLHttpRequest)或Fetch请求加载了包含目标数据的新数据。
微信扫一扫
支付宝扫一扫