使用 Python 和 Selenium 进行现代 Web 抓取
已发表: 2022-03-11几乎从万维网诞生之日起,Web 抓取就已用于从网站中提取数据。 在早期,抓取主要是在静态页面上完成的——那些具有已知元素、标签和数据的页面。
然而,最近,Web 开发中的先进技术使这项任务变得更加困难。 在本文中,我们将探讨在新技术和其他因素阻止标准抓取的情况下如何进行数据抓取。
传统数据抓取
由于大多数网站生成的页面旨在供人类阅读而不是自动阅读,因此网页抓取主要包括以编程方式消化网页的标记数据(想想右键单击,查看源代码),然后检测该数据中允许程序运行的静态模式“读取”各种信息并将其保存到文件或数据库中。
如果要找到报告数据,通常可以通过使用 URL 传递表单变量或参数来访问数据。 例如:
https://www.myreportdata.com?month=12&year=2004&clientid=24823
Python 已成为最流行的网络抓取语言之一,部分原因是为其创建了各种网络库。 一个流行的库,Beautiful Soup,旨在通过允许搜索、导航和修改标签(即解析树)从 HTML 和 XML 文件中提取数据。
基于浏览器的抓取
最近,我有一个看起来很简单的抓取项目,我已经做好了使用传统抓取来处理它的充分准备。 但随着深入,我发现了传统方法无法克服的障碍。
三个主要问题使我无法使用标准的抓取方法:
- 证书。 需要安装证书才能访问数据所在的网站部分。 访问初始页面时,出现提示,要求我选择计算机上安装的证书的正确证书,然后单击“确定”。
- 框架。 该网站使用了 iframe,这弄乱了我的正常抓取。 是的,我可以尝试查找所有 iframe URL,然后构建站点地图,但这似乎会变得笨拙。
- JavaScript。 在填写带有参数(例如,客户 ID、日期范围等)的表格后访问数据。 通常,我会绕过表单并简单地将表单变量(通过 URL 或作为隐藏表单变量)传递到结果页面并查看结果。 但在这种情况下,表单包含 JavaScript,它不允许我以正常方式访问表单变量。
所以,我决定放弃我的传统方法,寻找一种可能的基于浏览器的抓取工具。 这将与正常工作不同——我不是直接进入页面、下载解析树并提取数据元素,而是“像人一样”并使用浏览器访问我需要的页面,然后抓取数据 - 因此,绕过了处理提到的障碍的需要。
硒
一般来说,Selenium 以 Web 应用程序的开源测试框架而闻名——使 QA 专家能够执行自动化测试、执行回放和实现远程控制功能(允许许多浏览器实例进行负载测试和多种浏览器类型)。 就我而言,这似乎很有用。
我的网络抓取首选语言是 Python,因为它具有良好集成的库,通常可以处理所需的所有功能。 果然,有一个用于 Python 的 Selenium 库。 这将允许我实例化一个“浏览器”——Chrome、Firefox、IE 等——然后假装我自己正在使用浏览器来访问我正在寻找的数据。 如果我不希望浏览器实际出现,我可以在“无头”模式下创建浏览器,使其对任何用户都不可见。
项目设置
要开始试验,我需要设置我的项目并获得所需的一切。 我使用了一台 Windows 10 机器,并确保我有一个相对更新的 Python 版本(它是 v. 3.7.3)。 我创建了一个空白 Python 脚本,然后加载了我认为可能需要的库,如果我还没有加载库,则使用 PIP(Python 的包安装程序)。 这些是我开始使用的主要库:
- 请求(用于发出 HTTP 请求)
- URLLib3(URL 处理)
- 美丽的汤(以防 Selenium 无法处理所有事情)
- Selenium(用于基于浏览器的导航)
我还向脚本添加了一些调用参数(使用 argparse 库),以便我可以使用各种数据集,从命令行使用不同的选项调用脚本。 其中包括客户 ID、从月/年和到月/年。
问题 1 – 证书
我需要做的第一个选择是我要告诉 Selenium 使用哪个浏览器。 由于我通常使用 Chrome,并且它基于开源 Chromium 项目(Edge、Opera 和 Amazon Silk 浏览器也使用),所以我想我会先尝试一下。
我可以通过添加我需要的库组件在脚本中启动 Chrome,然后发出几个简单的命令:
# Load selenium components from selenium import webdriver from selenium.webdriver.common.by import By from selenium.webdriver.support.ui import WebDriverWait, Select from selenium.webdriver.support import expected_conditions as EC from selenium.common.exceptions import TimeoutException # Establish chrome driver and go to report site URL url = "https://reportdata.mytestsite.com/transactionSearch.jsp" driver = webdriver.Chrome() driver.get(url)
由于我没有以无头模式启动浏览器,因此浏览器实际上出现了,我可以看到它在做什么。 它立即要求我选择一个证书(我之前安装的)。
首先要解决的问题是证书。 如何选择合适的并接受它以进入网站? 在我对脚本的第一次测试中,我得到了这个提示:
这不好。 我不想在每次运行脚本时手动单击“确定”按钮。
事实证明,我能够找到解决方法 - 无需编程。 虽然我希望 Chrome 能够在启动时传递证书名称,但该功能并不存在。 但是,如果您的 Windows 注册表中存在某个条目,Chrome 确实能够自动选择证书。 您可以将其设置为选择它看到的第一个证书,或者更具体。 因为我只加载了一个证书,所以我使用了通用格式。
因此,使用该设置,当我告诉 Selenium 启动 Chrome 并出现证书提示时,Chrome 将“自动选择”证书并继续。
问题 2 – iframe
好的,现在我在网站上,出现了一个表单,提示我输入客户 ID 和报告的日期范围。
通过在开发人员工具 (F12) 中检查表单,我注意到表单是在 iframe 中呈现的。 因此,在我开始填写表单之前,我需要“切换”到表单所在的正确 iframe。 为此,我调用了 Selenium 的 switch-to 功能,如下所示:
# Switch to iframe where form is frame_ref = driver.find_elements_by_tag_name("iframe")[0] iframe = driver.switch_to.frame(frame_ref)
很好,所以现在在正确的框架中,我能够确定组件、填充客户 ID 字段并选择日期下拉列表:
# Find the Customer ID field and populate it element = driver.find_element_by_name("custId") element.send_keys(custId) # send a test id # Find and select the date drop-downs select = Select(driver.find_element_by_name("fromMonth")) select.select_by_visible_text(from_month) select = Select(driver.find_element_by_name("fromYear")) select.select_by_visible_text(from_year) select = Select(driver.find_element_by_name("toMonth")) select.select_by_visible_text(to_month) select = Select(driver.find_element_by_name("toYear")) select.select_by_visible_text(to_year)
问题 3 – JavaScript
表单上唯一剩下的就是“单击”“查找”按钮,因此它将开始搜索。 这有点棘手,因为 Find 按钮似乎是由 JavaScript 控制的,而不是普通的“提交”类型的按钮。 在开发人员工具中检查它,我找到了按钮图像,并且能够通过右键单击获得它的 XPath。

然后,有了这些信息,我在页面上找到了该元素,然后单击它。
# Find the 'Find' button, then click it driver.find_element_by_xpath("/html/body/table/tbody/tr[2]/td[1]/table[3]/tbody/tr[2]/td[2]/input").click()
瞧,表单提交了,数据出现了! 现在,我可以抓取结果页面上的所有数据并根据需要保存。 或者我可以吗?
获取数据
首先,我必须处理搜索一无所获的情况。 那很简单。 它会在不离开的情况下在搜索表单上显示一条消息,例如“未找到记录”。 我只是搜索了那个字符串,如果找到它就停在那里。
但如果结果确实出现了,数据会以加号 (+) 的形式显示在 div 中,以打开交易并显示其所有详细信息。 已打开的事务显示一个减号 (-),单击该减号会关闭 div。 单击加号将调用 URL 以打开其 div 并关闭任何打开的 div。
因此,有必要在页面上找到任何加号,收集每个加号旁边的 URL,然后遍历每个加号以获取每笔交易的所有数据。
# Loop through transactions and count links = driver.find_elements_by_tag_name('a') link_urls = [link.get_attribute('href') for link in links] thisCount = 0 isFirst = 1 for url in link_urls: if (url.find("GetXas.do?processId") >= 0): # URL to link to transactions if isFirst == 1: # already expanded + isFirst = 0 else: driver.get(url) # collapsed +, so expand # Find closest element to URL element with correct class to get tran type tran_type=driver.find_element_by_xpath("//*[contains(@href,'/retail/transaction/results/GetXas.do?processId=-1')]/following::td[@class='txt_75b_lmnw_T1R10B1']").text # Get transaction status status = driver.find_element_by_class_name('txt_70b_lmnw_t1r10b1').text # Add to count if transaction found if (tran_type in ['Move In','Move Out','Switch']) and (status == "Complete"): thisCount += 1
在上面的代码中,我检索到的字段是交易类型和状态,然后添加到计数中以确定有多少交易符合指定的规则。 但是,我可以检索交易详细信息中的其他字段,例如日期和时间、子类型等。
对于这个项目,计数被返回给调用应用程序。 但是,它和其他抓取的数据也可以存储在平面文件或数据库中。
其他可能的障碍和解决方案
使用您自己的浏览器实例抓取现代网站时可能会出现许多其他障碍,但大多数都可以解决。 这里有几个:
试图在它出现之前找到它
在浏览自己时,您发现自己等待页面出现的频率是多少,有时是几秒钟? 好吧,以编程方式导航时也会发生同样的情况。 你寻找一个类或其他元素——它不存在!
幸运的是,Selenium 有能力等到它看到某个元素,如果元素没有出现,它可以超时,如下所示:
element = WebDriverWait(driver, 10). until(EC.presence_of_element_located((By.ID, "theFirstLabel")))
通过验证码
一些网站使用 Captcha 或类似方法来防止不需要的机器人(他们可能会认为是你)。 这可以抑制卷筒纸的刮擦并减慢速度。
对于简单的提示(例如“什么是 2 + 3?”),这些通常可以很容易地阅读和计算出来。 但是,对于更高级的障碍,有一些库可以帮助尝试破解它。 一些示例包括 2Captcha、Death by Captcha 和 Bypass Captcha。
网站结构变化
网站注定要改变——而且他们经常这样做。 这就是为什么在编写抓取脚本时,最好记住这一点。 您需要考虑将使用哪些方法来查找数据,以及不使用哪些方法。 考虑部分匹配技术,而不是尝试匹配整个短语。 例如,网站可能会将消息从“未找到记录”更改为“未找到记录”——但如果您的匹配项是“未找到记录”,则应该没问题。 此外,请考虑是否匹配 XPATH、ID、名称、链接文本、标签或类名称,或 CSS 选择器 - 最不可能改变的。
总结:Python 和 Selenium
这是一个简短的演示,表明几乎任何网站都可以被抓取,无论使用什么技术以及涉及什么复杂性。 基本上,如果您可以自己浏览该网站,则通常可以被抓取。
现在,作为警告,这并不意味着每个网站都应该被抓取。 有些有合法的限制,并且有许多法庭案件决定了抓取某些网站的合法性。 另一方面,一些网站欢迎并鼓励从其网站检索数据,并且在某些情况下提供 API 以使事情变得更容易。
无论哪种方式,最好在开始任何项目之前检查条款和条件。 但是,如果您继续前进,请放心,您可以完成工作。
复杂网页抓取的推荐资源:
- 高级 Python Web Scraping:最佳实践和解决方法
- 可扩展的自己动手抓取:如何大规模构建和运行抓取工具