如何使用Python抓取需要登录的网站
在网络抓取时,您可能会发现一些数据只有在您登录后才可用。在本教程中,我们将学习使用的安全措施和三种有效的方法来抓取需要使用Python 登录的网站。
你能抓取需要登录的网站吗?
是的,在登录后进行网络抓取在技术上是可行的。但是您必须注意目标站点的抓取规则以及 GDPR 等遵守个人数据和隐私事宜的法律。
开始之前,必须了解一些有关HTTP 请求方法的一般知识。如果您不熟悉网络抓取,我们建议您阅读我们的Python 网络抓取指南以掌握基础知识。
如何使用 Python 登录网站?
抓取需要使用 Python 登录的网站的第一步是弄清楚目标域使用的登录类型。一些旧网站只需要发送用户名和密码。然而,现代网站使用更先进的安全措施。他们包括:
- 客户端验证。
- CSRF 令牌。
- Web 应用程序防火墙 (WAF)。
继续阅读以学习绕过这些严格安全保护的技术。
您如何在 Python 登录后抓取网站?
我们将看到使用 Python 逐步抓取站点登录背后的数据。我们将从只需要用户名和密码的表单开始,然后逐渐增加难度。
本教程中展示的方法仅用于教育目的。
需要简单用户名和密码登录的站点
我们假设您已经设置了 Python 3 和 Pip,否则您应该查看有关正确安装 Python 的指南。
作为依赖项,我们将使用Requests和BeautifulSoup库。从安装它们开始:
pip install requests beautifulsoup4
提示:如果您在安装过程中遇到任何问题,请访问此页面获取 Requests,并访问此页面获取 Beautiful Soup。
现在,转到Acunetix 的用户信息。这是一个专门为学习目的而制作的测试页面,受简单登录保护,因此您将被重定向到登录页面。
在继续之前,我们将分析尝试登录时发生的情况。为此,使用test
用户名和密码,点击登录按钮并检查浏览器上的网络部分。
提交表单会生成POST
对用户信息页面的请求,服务器会用 cookie 进行响应并完成请求的部分。下面的屏幕截图显示了标头、负载、响应和 cookie。
以下网络抓取脚本将绕过登录。它创建一个类似的负载并将请求发布到用户信息页面。响应到达后,程序使用 Beautiful Soup 解析响应文本并打印页面名称。
from bs4 import BeautifulSoup as bs import requests URL = "http://testphp.vulnweb.com/userinfo.php" payload = { "uname": "test", "pass": "test" } s = requests.session() response = s.post(URL, data=payload) print(response.status_code) # If the request went Ok we usually get a 200 status. from bs4 import BeautifulSoup soup = BeautifulSoup(response.content, "html.parser") protected_content = soup.find(attrs={"id": "pageName"}).text print(protected_content)
这是我们的输出:
伟大的!🎉 您刚刚学习了使用 Python 在简单登录后抓取网站。现在,让我们尝试使用更复杂的保护措施。
使用 CSRF 令牌身份验证登录网站
2023年,登录网站不是那么容易的。大多数网站都实施了额外的安全措施来阻止黑客和恶意机器人。其中一项措施需要在身份验证过程中使用 CSRF(跨站点请求伪造)令牌。
要了解您的目标网站是否需要 CSRF 或authenticity_token
,请充分利用浏览器的开发人员工具。无论您使用 Safari、Chrome、Edge、Chromium 还是 Firefox 都没有关系,因为它们都为开发人员提供了一组相似的强大工具。要了解更多信息,我们建议查看Chrome DevTools或Mozilla DevTools文档。
让我们开始抓取 GitHub!
第 1 步:登录 GitHub 帐户
GitHub 是使用 CSRF 令牌身份验证进行登录的网站之一。我们将抓取测试帐户中的所有存储库以进行演示。
打开网络浏览器(在我们的例子中是 Chrome)并导航到GitHub 的登录页面。现在,按下F12
键在浏览器中查看 DevTools 窗口并检查页面的 HTML 以检查登录表单元素是否具有 action 属性:
Network
从 DevTools 窗口中选择选项卡并单击Sign in
按钮,然后自行填写并提交表单。这将执行几个 HTTP 请求,在此选项卡中可见。
让我们通过查看刚刚发送的命名请求来查看单击“登录”按钮后得到的内容。POST
session
在该Headers
部分中,您将找到发布登录凭据的完整 URL。我们将使用它在我们的脚本中发送登录请求。
第 2 步:为受 CSRF 保护的登录请求设置有效负载
现在,您可能想知道我们如何知道存在 CSRF 保护。答案就在我们面前:
导航到请求Payload
的部分session
。请注意,除了login
和之外password
,我们还有身份验证令牌和时间戳的有效负载数据。此真实性令牌是 CSRF 令牌,必须作为有效负载随登录POST
请求传递。
从每个新登录请求的部分手动复制这些字段Payload
非常繁琐。我们肯定会编写代码以编程方式获取它。
接下来,再次查看登录表单的 HTML 源代码。您会看到所有Payload
字段都出现在表单中。
以下脚本从登录页面获取CSRF token
,timestamp
和:timestamp_secret
import requests from bs4 import BeautifulSoup login_url = "https://github.com/session" login = "Your Git username Here" password = "Your Git Password Here" with requests.session() as s: req = s.get(login_url).text html = BeautifulSoup(req,"html.parser") token = html.find("input", {"name": "authenticity_token"}). attrs["value"] time = html.find("input", {"name": "timestamp"}).attrs["value"] timeSecret = html.find("input", {"name": "timestamp_secret"}). attrs["value"]
我们现在可以payload
为我们的 Python 登录请求填充我们的字典:
payload = { "authenticity_token": token, "login": login, "password": password, "timestamp": time, "timestamp_secret": timeSecret }
注意:如果您在 HTML 中找不到 CSRF 令牌,它可能保存在 cookie 中。在基于 Chromium 的浏览器(如 Chrome)中,从 DevTools 转到选项Application
卡。然后,在左侧面板中,搜索cookies
并选择目标网站的域。
第 3 步:设置标题
可以通过简单地发送请求来访问需要登录的POST
网站payload
。然而,单独使用这种方法来抓取具有高级安全措施的网站是天真的,因为它们通常足够聪明,可以识别非人类行为。因此,可能有必要采取措施使爬虫看起来比机器人更人性化。
最基本和最现实的方法是将真实的浏览器标头添加到我们的请求中。从浏览器请求的选项卡中复制标头Headers
,并将其添加到 Python 登录请求中。您可能需要了解有关请求标头设置的更多信息。
或者,您可以使用网络抓取 API来为您绕过大量烦人的反机器人系统。
第 4 步:登录操作
这是我们的幸运日,因为您不需要为 GitHub 添加标头,所以我们已准备好通过 Python 发送登录请求:
res = s.post(login_url, data=payload) print(res.url)
如果登录成功,则输出为https://github.com/
,https://github.com/session
否则为。
👍 太棒了,我们刚刚确定了一个受 CSRF 保护的登录绕过!现在让我们抓取受保护的 git 存储库中的数据。
第 5 步:抓取受保护的 GitHub 存储库
回想一下,我们在较早的代码中使用with requests.session() as s:
创建请求会话的语句开始。通过会话中的请求登录后,您无需为同一会话中的后续请求重新登录。
是时候访问存储库了。生成一个GET
,然后使用 BeautifulSoup 解析响应。
repos_url = "https://github.com/" + login + "/?tab=repositories" r = s.get(repos_url) soup = BeautifulSoup(r.content, "html.parser")
我们将提取用户名和存储库列表。
首先,对于用户名,导航到浏览器中的存储库页面,然后右键单击用户名并选择Inspect Element
。用户名包含在 span 元素中,CSS 类在标签p-nickname vcard-username d-block
内命名<h1>
。
其次,对于存储库,右键单击任何存储库名称并选择Inspect Element
. DevTools 窗口将显示以下内容:
存储库的名称位于<h3>
带有 class 的标签中的超链接内wb-break-all
。好的,我们现在对目标元素有了足够的了解,所以让我们提取它们:
usernameDiv = soup.find("span", class_="p-nickname vcard-username d-block") print("Username: " + usernameDiv.getText()) repos = soup.find_all("h3",class_="wb-break-all") for r in repos: repoName = r.find("a").getText() print("Repository Name: " + repoName)
由于可以在目标网页上找到多个存储库,因此脚本使用该find_all()
方法提取所有。为此,循环遍历每个<h3>
标签并打印所附标签的文本<a>
。
完整代码如下所示:
import requests from bs4 import BeautifulSoup login = "Your Username Here" password = "Your Password Here" login_url = "https://github.com/session" repos_url = "https://github.com/" + login + "/?tab=repositories" with requests.session() as s: req = s.get(login_url).text html = BeautifulSoup(req,"html.parser") token = html.find("input", {"name": "authenticity_token"}).attrs["value"] time = html.find("input", {"name": "timestamp"}).attrs["value"] timeSecret = html.find("input", {"name": "timestamp_secret"}).attrs["value"] payload = { "authenticity_token": token, "login": login, "password": password, "timestamp": time, "timestamp_secret": timeSecret } res =s.post(login_url, data=payload) r = s.get(repos_url) soup = BeautifulSoup (r.content, "html.parser") usernameDiv = soup.find("span", class_="p-nickname vcard-username d-block") print("Username: " + usernameDiv.getText()) repos = soup.find_all("h3", class_="wb-break-all") for r in repos: repoName = r.find("a").getText() print("Repository Name: " + repoName)
输出:
我们刚刚抓取了一个 CSRF 认证的网站。
在受 WAF 保护的网站上抓取登录信息
在许多网站上,在发送正确的用户、密码和 CSRF 令牌后,您仍然会进入拒绝访问屏幕或收到 403 等 HTTP 错误。即使使用正确的请求标头也行不通。这表明该网站使用了高级保护措施,例如客户端浏览器验证。
客户端验证是一种阻止机器人和爬虫访问网站的安全措施,主要由 WAF(Web 应用程序防火墙)实施,如Cloudflare、Akamai和PerimeterX。
让我们看看如何找到解决方案。
使用 Selenium 的基本 WAF 保护
如果您仅使用 Requests 和 BeautifulSoup 库来处理需要类似人类交互的登录,那么被阻止的风险就太高了。替代方案?无头浏览器。它们是您所知道的标准浏览器,例如 Chrome 或 Firefox,但它们没有任何可供人类用户交互的 GUI。它们的美妙之处在于它们可以通过编程方式进行控制。
人们发现诸如 Selenium 之类的无头浏览器可以很好地绕过 WAF 的基本登录保护。此外,它们使您能够登录在登录过程中使用两步验证(您输入电子邮件,然后出现密码字段)的网站,例如 Twitter。
Selenium 有一组工具可以帮助您创建无头浏览器实例并使用代码控制它。尽管基本的 Selenium 实现不足以抓取受 WAF 保护的站点,但可以使用一些扩展库来帮助我们实现此目的。undetected-chromedriver
是一个不可检测的 ChromeDriver 自动化库,它使用多种规避技术来避免检测。我们将在本教程中进行。
我们这个案例的目标站点是 DataCamp,这是一个面向数据分析爱好者的电子学习网站,它有一个两步登录。我们会这样做:
- 在DataCamp上创建一个帐户并注册 Python 课程,以便接下来抓取我们的数据。
- 使用登录到 DataCamp
undetected-chromedriver
。 - 导航和抓取
https://app.datacamp.com/learn
。 - 从解析的 HTML 中提取个人资料名称和注册课程。
让我们从安装和导入所需的模块和库开始。
pip install selenium undetected-chromedriver
import undetected_chromedriver as uc import time from selenium.webdriver.common.by import By
现在,使用该对象创建一个不可检测的无头浏览器实例uc
并移至登录页面。
chromeOptions = uc.ChromeOptions() chromeOptions.headless = true driver = uc.Chrome(use_subprocess=True, options=chromeOptions) driver.get("https://www.datacamp.com/users/sign_in")
要以编程方式输入电子邮件和密码字段,您需要id
从登录表单中获取输入字段的 。为此,请在浏览器中打开登录页面并右键单击电子邮件字段以检查该元素。这将在 DevTools 窗口中打开相应的 HTML 代码。
以下屏幕截图显示了电子邮件字段的 HTML 源代码,这是我们需要的第一个:
由于登录遵循两步过程,我们最初只有Email address
表单上的字段带有id="user_email"
. 让我们以编程方式填充它并单击Next
按钮。
uname = driver.find_element(By.ID, "user_email") uname.send_keys("Your Email Here") driver.find_element(By.CSS_SELECTOR, ".js-account-check-email").click() time.sleep(10)
请注意,添加 10 秒的休眠是为了让 JavaScript 动态加载该Password
字段。
以下代码输入密码并点击提交按钮请求登录:
passwordF = driver.find_element(By.ID, "user_password") passwordF.send_keys("Your Password Here") driver.find_element(By.NAME, "commit").click()
您已登录。
无头实例成功登录后,您可以转到仪表板中可用的任何网页。由于我们想从仪表板页面抓取个人资料名称和注册课程,我们将在以下屏幕截图所示的位置找到它们:
下面的代码将检索并解析目标 URL 以显示个人资料名称和注册课程。
driver.get("https://app.datacamp.com/learn") myName = driver.find_element(By.CLASS_NAME, "mfe-app-learn-hub-15alavv") myCourse = driver.find_element(By.CLASS_NAME, "mfe-app-learn-hub-1f1m67o") print("Profile Name: " + myName.get_attribute("innerHTML")) print("Course Enrolled: " + myCourse.get_attribute("innerHTML")) driver.close()
让我们结合之前的所有代码块,看看完整的抓取脚本是什么样子的。
import undetected_chromedriver as udc import time from selenium.webdriver.common.by import By username="Your Username Here"; password="Your Password Here" chromeOptions = udc.ChromeOptions() chromeOptions.headless = True driver = udc.Chrome(use_subprocess=True, options=chromeOptions) driver.get("https://www.datacamp.com/users/sign_in") uname = driver.find_element(By.ID, "user_email") uname.send_keys(username) driver.find_element(By.CSS_SELECTOR, ".js-account-check-email").click() time.sleep(5) passwordF = driver.find_element(By.ID, "user_password") passwordF.send_keys(password) driver.find_element(By.NAME, "commit").click() time.sleep(2) driver.get("https://app.datacamp.com/learn") time.sleep(2) myName = driver.find_element(By.CLASS_NAME, "mfe-app-learn-hub-15alavv") myCourse = driver.find_element(By.CLASS_NAME, "mfe-app-learn-hub-1f1m67o") print("Profile Name: " + myName.get_attribute("innerHTML")) print("Course Enrolled: " + myCourse.get_attribute("innerHTML")) driver.close()
我们建议更改headless
选项以False
了解后面发生了什么。根据您的个人资料名称和注册课程,输出应如下所示:
我们刚刚抓取了受 WAF 保护的登录背后的内容。但是每个网站都一样吗?
目前,该undetected-chromedriver
软件包仅支持 109 或更高版本的 Chromium 浏览器。此外,受 WAF 保护的站点可以轻松检测到其无头模式。
抓取需要使用 Python 登录的网站,undetected-chromedriver
如果保护是基本的,可能就足够了。但我们假设该站点使用高级 Cloudflare 保护(例如G2)或其他 DDoS 缓解服务。在这种情况下,我们看到的解决方案可能并不可靠。
使用 ZenRows 的高级保护
在具有更高保护措施的网站上抓取登录后的内容需要正确的工具。为此,我们将使用ZenRows API。
我们的任务包括绕过G2.com 的登录页面,这是两步登录的第一步,然后在我们登录后从主页提取欢迎消息。
但在开始编写代码之前,让我们首先使用 DevTools 探索我们的目标。下表列出了有关我们将在整个脚本中与之交互的 HTML 元素的必要信息。请记住接下来的步骤。
Element/Purpose | Element Type | Attribute | Value |
---|---|---|---|
G2 login (step 1): Email input | <input type=”email”> | Class | input-group-field |
G2 login (step 1): Next button to proceed to the next login step | <button> | Class | js-button-submit |
G2 login (Step 2): Password Field | <input type=”password”> | Id | password_input |
G2 login (Step 2): Login form submit button | <input type=”submit”> | CSS Selector | input[value='Sign In'] |
Welcome message at Homepage | <div> | Class | l4 color-white my-1 |
使用 ZenRows,您不需要安装任何特定的浏览器驱动程序(就像使用 Selenium 一样)。此外,您无需担心高级 Cloudflare 保护、身份泄露和其他 DDoS 缓解服务。此外,这个可扩展的 API 使您免于基础架构可扩展性问题。
只需免费注册即可访问 Request Builder 并按照下面的屏幕截图填写详细信息。
下面一步步说说请求的创建:
- 设置初始目标(即在我们的案例中为G2 登录页面)。
- 选择纯 HTML。我们稍后将在代码中使用 BeatifulSoup 进一步解析它。如果您愿意,可以使用
CSS Selectors
仅从目标中抓取一些特定元素。 - 设置高级代理可帮助您抓取特定区域的数据并保护您免受身份泄露。
- 设置JavaScript 呈现对于在第 6 步中运行某些 JavaScript 指令是强制性的。
- 选择Antibot可帮助您绕过高级 WAF 安全措施。
- 检查JavaScript 指令允许您添加编码的JavaScript 指令字符串以在目标上运行。它允许类似于无头浏览器的控件。
- 当您选中 JavaScript Instructions 复选框时,将出现一个文本框。您可以编写任意数量的 JS 指令,我们放置以下指令:在我们的例子中:
[ {"wait": 2000}, {"evaluate": "document.querySelector('.input-group-field').value = 'Your Business Email Here';"}, {"wait": 1000}, {"click": ".js-button-submit"}, {"wait": 2000}, {"evaluate": "document.querySelector('#password_input').value = 'Your Password Here';"}, {"wait": 1000}, {"click": "input[value='Sign In']"}, {"wait": 6000} ]
注意:通过添加您自己的登录凭据来更新上面的代码。
- 选择Python。
- 选择 SDK 并复制整个代码。请记住使用安装 ZenRows SDK 包
pip install zenrows
。
现在,您可以将此代码粘贴到您的 Python 项目中并执行它。我们复制了 SDK 代码并对其进行了修改,使其更易于移植和更易于理解。
# pip install zenrows from zenrows import ZenRowsClient import urllib import json client = ZenRowsClient("Your Zenrows API Goes Here") url = "https://www.g2.com/login?form=signup#state.email.showform" js_instructions = [ {"wait": 2000}, {"evaluate": "document.querySelector('.input-group-field').value = 'Your G2 Login Email Here';"}, {"wait": 1000}, {"click": ".js-button-submit"}, {"wait": 2000}, {"evaluate": "document.querySelector('#password_input').value = 'Your G2 Password Here';"}, {"wait": 1000}, {"click": "input[value='Sign In']"}, {"wait": 6000} ] params = { "js_render":"true", "antibot":"true", "js_instructions":urllib.parse.quote(json.dumps(js_instructions)), "premium_proxy":"true" } response = client.get(url, params=params) print(response.text)
该代码片段在登录后从G2 主页获取并打印纯 HTML 。现在,我们将使用BeatifulSoup 进一步解析 HTML 并提取我们想要的数据。
from bs4 import BeautifulSoup soup = BeautifulSoup(response.text, "html.parser") welcome = soup.find("div", attrs={"class", "l4 color-white my-1"}) print(welcome.text)
这是完整的代码:
# pip install zenrows from zenrows import ZenRowsClient from bs4 import BeautifulSoup import urllib import json client = ZenRowsClient("Your Zenrows API Goes Here") url = "https://www.g2.com/login?form=signup#state.email.showform" js_instructions = [ {"wait": 2000}, {"evaluate": "document.querySelector('.input-group-field').value = 'Your G2 Login Email Here';"}, {"wait": 1000}, {"click": ".js-button-submit"}, {"wait": 2000}, {"evaluate": "document.querySelector('#password_input').value = 'Your G2 Password Here';"}, {"wait": 1000}, {"click": "input[value='Sign In']"}, {"wait": 6000} ] params = { "js_render":"true", "antibot":"true", "js_instructions":urllib.parse.quote(json.dumps(js_instructions)), "premium_proxy":"true" } response = client.get(url, params=params) soup = BeautifulSoup(response.text, "html.parser") welcome = soup.find("div", attrs={"class", "l4 color-white my-1"}) print(welcome.text)
结论
什么可以抓取需要使用 Python 登录的网站?正如所见,使用 BeautifulSoup 检查 HTML 并使用 Requests 库获取 cookie 可以帮助您。但是,对于具有强大反机器人解决方案的现代网站,您需要无法检测到的无头浏览器。它们的问题是可扩展性、成本和性能限制。此外,它们仍然可能被实施了高级 WAF 的网站阻止。