Python Web页面抓取

Python Web页面抓取:从网站抓取数据

抓取应该是关于从 HTML 中提取内容。这听起来很简单,但有很多障碍。第一个是获取上述 HTML。为此,我们将使用Python 来避免检测

如果你去过那里,你就会知道它可能需要绕过反机器人系统。使用 Python 或任何其他工具在不被阻止的情况下进行 Web 抓取并不是在公园散步。

网站倾向于保护他们的数据和访问。防御系统可以采取许多可能的行动。和我们一起学习如何减轻它们的影响。或者使用 Python RequestsPlaywright直接绕过机器人检测

注意:大规模测试时,切勿直接使用您的家庭 IP。一个小错误或失误,您将被禁止。

准备工作

要使代码正常工作,您需要安装 python3。有些系统已经预装了它。之后,通过运行安装所有必需的库pip install

pip install requests playwright 
npx playwright install

IP 速率限制

最基本的安全系统是禁止或限制来自同一 IP 的请求。这意味着普通用户不会在几秒钟内请求一百个页面,因此他们继续将该连接标记为危险。

import requests 
 
response = requests.get('http://httpbin.org/ip') 
print(response.json()['origin']) 
# xyz.84.7.83

IP 速率限制与 API 速率限制的工作方式类似,但通常没有关于它们的公开信息。我们无法确定我们可以安全地处理多少请求。

我们的互联网服务提供商为我们分配了我们无法影响或屏蔽的 IP。解决办法是改变它。我们不能修改一台机器的IP,但是我们可以使用不同的机器。数据中心可能有不同的 IP,尽管这不是真正的解决方案。

旋转代理

即使我们不推荐,也有免费代理。它们可能适用于测试,但并不可靠。我们可以使用其中一些进行测试,正如我们将在一些示例中看到的那样。

现在我们有了不同的 IP,而且我们的家庭连接安全无虞。好的。但是,如果他们阻止代理的 IP 怎么办?我们回到了最初的位置。

我们不会详细介绍免费代理。只需使用列表中的下一个。经常更换它们,因为它们的使用寿命通常很短。

另一方面,付费代理服务提供IP 轮换。我们的服务会以同样的方式工作,但网站会看到不同的 IP。在某些情况下,他们会针对每个请求或每隔几分钟轮换一次。无论如何,它们更难被禁止。当它发生时,我们将在短时间后获得一个新的 IP。

import requests 
 
proxies = {'http': 'http://190.64.18.177:80'} 
response = requests.get('http://httpbin.org/ip', proxies=proxies) 
print(response.json()['origin']) # 190.64.18.162

我们知道这些;这意味着机器人检测服务也知道它们。一些大公司会阻止来自已知代理 IP 或数据中心的流量。对于这些情况,有更高的代理级别:住宅

更昂贵且有时带宽受限的住宅代理为我们提供了普通人使用的 IP。这意味着我们的移动提供商明天可以为我们分配该 IP。或者朋友昨天有。他们与实际的最终用户没有区别。

我们可以刮任何我们想要的,对吧?默认情况下便宜的,必要时昂贵的。不,还没有。我们只通过了第一个障碍,还有更多障碍。我们必须看起来像合法用户,以免被标记为机器人或爬虫。

用户代理标头

下一步是检查我们的请求标头。最著名的是User-Agent(简称 UA),但还有更多。UA 遵循我们稍后会看到的格式,许多软件工具都有自己的格式,例如 GoogleBot。如果我们直接使用 Python Requests 或 cURL,目标网站将收到以下内容。

import requests 
 
response = requests.get('http://httpbin.org/headers') 
print(response.json()['headers']['User-Agent']) 
# python-requests/2.25.1
curl http://httpbin.org/headers # { ... "User-Agent": "curl/7.74.0" ... }

许多网站不会检查 UA,但对于那些这样做的网站来说,这是一个巨大的危险信号。我们将不得不伪造它。幸运的是,大多数库都允许自定义标头。按照使用请求的示例:

import requests 
 
headers = {"User-Agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.96 Safari/537.36"} 
response = requests.get('http://httpbin.org/headers', headers=headers) 
print(response.json()['headers']['User-Agent']) # Mozilla/5.0 ...

要获取您当前的用户代理,请访问httpbin – 就像代码片段所做的那样 – 并复制它。请求具有相同 UA 的所有 URL 也可能会触发一些警报,使解决方案有点复杂。理想情况下,我们将拥有所有当前可能的用户代理,并像处理 IP那样轮换它们。由于这几乎是不可能的,我们至少可以拥有一些。有可供我们选择的用户代理列表。

import requests 
import random 
 
user_agents = [ 
    'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36', 
    'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.107 Safari/537.36', 
    'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.212 Safari/537.36', 
    'Mozilla/5.0 (iPhone; CPU iPhone OS 12_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148', 
    'Mozilla/5.0 (Linux; Android 11; SM-G960U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.72 Mobile Safari/537.36' 
] 
user_agent = random.choice(user_agents) 
headers = {'User-Agent': user_agent} 
response = requests.get('https://httpbin.org/headers', headers=headers) 
print(response.json()['headers']['User-Agent']) 
# Mozilla/5.0 (iPhone; CPU iPhone OS 12_2 like Mac OS X) ...

请记住,浏览器经常更改版本,此列表可能会在几个月后过时。如果我们要使用 User-Agent 轮换,可靠的来源是必不可少的。我们可以手工完成或使用服务提供商。

我们更近了一步,但标头中仍然存在一个缺陷:反机器人系统也知道这一技巧并检查其他标头以及 User-Agent。

全套标题

每个浏览器,甚至是版本,都会发送不同的标头。检查 Chrome 和 Firefox 的运行情况:

{ 
    "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9", 
    "Accept-Encoding": "gzip, deflate, br", 
    "Accept-Language": "en-US,en;q=0.9", 
    "Host": "httpbin.org", 
    "Sec-Ch-Ua": ""Chromium";v="92", " Not A;Brand";v="99", "Google Chrome";v="92"", 
    "Sec-Ch-Ua-Mobile": "?0", 
    "Sec-Fetch-Dest": "document", 
    "Sec-Fetch-Mode": "navigate", 
    "Sec-Fetch-Site": "none", 
    "Sec-Fetch-User": "?1", 
    "Upgrade-Insecure-Requests": "1", 
    "User-Agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.107 Safari/537.36" 
}
{ 
    "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8", 
    "Accept-Encoding": "gzip, deflate, br", 
    "Accept-Language": "en-US,en;q=0.5", 
    "Host": "httpbin.org", 
    "Sec-Fetch-Dest": "document", 
    "Sec-Fetch-Mode": "navigate", 
    "Sec-Fetch-Site": "none", 
    "Sec-Fetch-User": "?1", 
    "Upgrade-Insecure-Requests": "1", 
    "User-Agent": "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:90.0) Gecko/20100101 Firefox/90.0" 
}

这意味着你认为它意味着什么。之前包含 5 个用户代理的数组不完整。我们需要一个包含每个用户代理的完整标头集的数组。为简洁起见,我们将显示一个包含一项的列表。已经够长了。

在这种情况下,从 httpbin 复制结果是不够的。理想的情况是直接从源代码中复制它。最简单的方法是使用 Firefox 或 Chrome DevTools – 或浏览器中的等效工具。转到“网络”选项卡,访问目标网站,右键单击请求并复制为 cURL。然后将 curl 语法转换为 Python,并将标头粘贴到列表中。

import requests 
import random 
 
headers_list = [{ 
    'authority': 'httpbin.org', 
    'cache-control': 'max-age=0', 
    'sec-ch-ua': '"Chromium";v="92", " Not A;Brand";v="99", "Google Chrome";v="92"', 
    'sec-ch-ua-mobile': '?0', 
    'upgrade-insecure-requests': '1', 
    'user-agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.107 Safari/537.36', 
    'accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9', 
    'sec-fetch-site': 'none', 
    'sec-fetch-mode': 'navigate', 
    'sec-fetch-user': '?1', 
    'sec-fetch-dest': 'document', 
    'accept-language': 'en-US,en;q=0.9', 
} # , {...} 
] 
headers = random.choice(headers_list) 
response = requests.get('https://httpbin.org/headers', headers=headers) 
print(response.json()['headers'])

我们可以添加一个 Referer 标头以获得额外的安全性 – 例如 Google 或来自同一网站的内部页面。它会掩盖这样一个事实,即我们总是在没有交互的情况下直接请求 URL。但要小心,因为添加引荐来源网址会更改更多标头。您不希望您的 Python 请求脚本被这样的错误阻塞。

cookie

我们忽略了上面的cookie,因为它们应该单独一节。Cookie 可以帮助您绕过某些反机器人程序或阻止您的请求。它们是我们需要正确理解的强大工具。

例如,Cookie 可以跟踪用户会话并在登录后记住该用户。网站为每个新用户分配一个 cookie DTijCmU5aS98c6gihFDmkSUmKgTCXBGHrXrHXJv61aXf cookie,跟踪用户活动。

这怎么成问题了?我们正在使用轮换代理,因此每个请求可能有来自不同地区或国家的不同 IP。Antibots 可以看到该模式并阻止它,因为它不是用户浏览的自然方式。

另一方面,一旦绕过反机器人解决方案,它就会发送有价值的 cookie。如果会话看起来合法,防御系统不会检查两次。查看如何绕过 Cloudflare以获取更多信息。

cookie 会帮助我们的 Python 请求脚本避免机器人检测吗?还是他们会伤害我们并阻止我们?答案在于我们的实施。

对于简单的情况,不发送 cookie 可能效果最好。无需维护会话。

对于更高级的案例和反机器人软件,会话 cookie 可能是获取和抓取最终内容的唯一方法。始终考虑到会话请求和 IP 必须匹配。

如果我们希望在 XHR 调用后在浏览器中生成内容,也会发生同样的情况。我们需要使用无头浏览器。初始加载后,Javascript 将尝试使用 XHR 调用获取一些内容。在受保护的网站上,如果没有 cookie,我们将无法进行该调用。

我们将如何使用无头浏览器,特别是 Playwright,来避免检测?继续阅读!

无头浏览器

一些反机器人系统只会在浏览器解决 Javascript 挑战后才显示内容。而且我们不能使用 Python 请求来模拟那样的浏览器行为。我们需要一个执行 Javascript 的浏览器来运行并通过挑战。

SeleniumPuppeteer和 Playwright 是最常用和最知名的库。出于性能原因,避免使用它们会更好,而且它们会使抓取速度变慢。但有时,别无选择。

我们将看到如何运行 Playwright。下面的代码片段显示了一个简单的脚本,该脚本访问了打印已发送标头的页面。输出仅显示 User-Agent,但由于它是一个真正的浏览器,因此标头将包括整个集合(Accept、Accept-Encoding 等)。

import json 
from playwright.sync_api import sync_playwright 
 
with sync_playwright() as p: 
    for browser_type in [p.chromium, p.firefox, p.webkit]: 
        browser = browser_type.launch() 
        page = browser.new_page() 
        page.goto('https://httpbin.org/headers') 
        jsonContent = json.loads(page.inner_text('pre')) 
        print(jsonContent['headers']['User-Agent']) 
        browser.close() 
 
# Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/93.0.4576.0 Safari/537.36 
# Mozilla/5.0 (X11; Linux x86_64; rv:90.0) Gecko/20100101 Firefox/90.0 
# Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.0 Safari/605.1.15

这种方法有其自身的问题:看看用户代理。Chromium 包含HeadlessChrome,它会告诉目标网站,嗯,它是一个无头浏览器。他们可能会据此采取行动。

回到标头部分:我们可以添加自定义标头来覆盖默认标头。用这一行替换上一个代码片段中的行并粘贴一个有效的用户代理:

browser.new_page(extra_http_headers={'User-Agent': '...'})

那只是无头浏览器的入门级。Headless detection本身就是一个领域,很多人都在做这方面的工作。有些是为了检测它,有些是为了避免被阻止。例如,您可以使用实际浏览器和无头浏览器访问pixelscan 。要被视为“始终如一”,您需要努力工作。

请看下面的屏幕截图,这是在使用 Playwright 访问 pixelscan 时拍摄的。看到UA了吗?我们伪造的没问题,但他们可以通过检查导航器 Javascript API 来检测我们在说谎。像素扫描不一致

我们可以通过user_agent,剧作家会在javascript中为我们设置用户代理和标头。

page = browser.new_page(user_agent='...')

对于更高级的案例,您可以轻松地将Playwright 隐身添加到您的脚本中并使检测更加困难。它处理标头和浏览器 Javascript API 之间的不一致等问题。

总之,拥有 100% 的覆盖率很复杂,但大多数时候您不需要它。网站总是可以做一些更复杂的检查:WebGL、触摸事件或电池状态。

除非尝试抓取需要绕过反机器人解决方案(如 Akamai)的网站,否则您将不需要这些额外功能。对于这些情况,额外的努力将是强制性的。老实说,要求很高。

地理限制或地理封锁

你有没有试过在美国以外的地方收看 CNN?
CNN 地理封锁

这就是所谓的地理封锁。只有来自美国境内的连接才能观看 CNN 直播。为了绕过它,我们可以使用虚拟专用网络 (VPN)。然后我们可以像往常一样浏览,但是由于 VPN,该网站将看到一个本地 IP。

使用地理封锁抓取网站时也会发生同样的情况。代理有一个等价物:geolocated proxies。一些代理提供商允许我们从国家列表中进行选择。例如,激活后,我们将只能从美国获得本地 IP。

行为模式

如今,阻止 IP 和用户代理是不够的。它们会在数小时甚至数分钟内变得难以管理和陈旧。只要我们使用干净的IP 和真实世界的用户代理执行请求,我们基本上是安全的。涉及的因素更多,但大多数请求应该是有效的。

然而,大多数现代反机器人软件使用机器学习和行为模式,而不仅仅是静态标记(IP、UA、地理定位)。这意味着如果我们总是以相同的顺序执行相同的动作,我们就会被发现

  1. 转到主页
  2. 点击“商店”按钮
  3. 向下滚动
  4. 转到第 2 页

几天后,启动相同的脚本可能会导致每个请求都被阻止。许多人都可以执行这些相同的操作,但机器人有一些让它们显而易见的东西:速度。使用软件,我们会按顺序执行每个步骤,而实际用户会花一秒钟,然后单击,使用鼠标滚轮缓慢向下滚动,将鼠标移到链接上并单击。

也许没有必要假装这一切,但要意识到可能出现的问题并知道如何面对它们。

我们必须思考我们想要什么。也许我们不需要第一个请求,因为我们只需要第二页。我们可以将其用作入口点,而不是主页。并保存一个请求。它可以扩展到每个域数百个 URL。无需按顺序访问每一页,向下滚动,单击下一页并重新开始。

要抓取搜索结果,一旦我们识别出分页的 URL 模式,我们只需要两个数据点:项目数和每页项目数。大多数时候,该信息出现在首页或请求中。

import requests 
from bs4 import BeautifulSoup 
 
response = requests.get('https://scrapeme.live/shop/') 
soup = BeautifulSoup(response.content, 'html.parser') 
pages = soup.select('.woocommerce-pagination a.page-numbers:not(.next)') 
print(pages[0].get('href')) # https://scrapeme.live/shop/page/2/ 
print(pages[-1].get('href')) # https://scrapeme.live/shop/page/48/

一个请求告诉我们有 48 页。我们现在可以排队了。结合其他技术,我们将从该页面抓取内容并添加剩余的 47 个内容。要绕过反机器人系统抓取它们,我们可以:

  • 打乱页面顺序以避免模式检测
  • 使用不同的 IP 和 User-Agent,因此每个请求看起来都是一个新请求
  • 在某些呼叫之间添加延迟
  • 随机使用谷歌作为推荐人

我们可以编写一些混合所有这些的代码片段,但现实生活中最好的选择是使用一个工具,如Scrapypyspidernode-crawler (Node.js) 或Colly (Go)。作为片段的想法是单独理解每个问题。但是对于大型的、现实生活中的项目,我们自己处理一切就太复杂了。

验证码

即使是准备最充分的请求也可能被捕获并显示验证码。现在,解决验证码是可以实现的——Anti-Captcha 和 2Captcha——但是浪费时间和金钱。最好的解决办法是避开它们。次要的是忘记该请求并重试。

例外是显而易见的:网站总是在第一次访问时显示验证码。如果没有办法绕过它,我们必须解决它。然后,使用会话 cookie 以避免再次受到挑战

这听起来可能违反直觉,但等待一秒钟并使用不同的 IP 和标头集重试相同的请求将比解决验证码更快。亲自尝试并告诉我们体验 😉。

做一个优秀的网络公民

我们可以使用多个网站进行测试,但在进行大规模测试时要小心。努力成为一名优秀的互联网公民,不要造成 – 小 – DDoS。限制每个域的交互。亚马逊每秒可以处理数千个请求。但并非所有目标站点都会。

我们总是在谈论“只读”浏览模式。访问页面并阅读其内容。切勿出于恶意提交表格或执行主动操作。

如果我们采取更积极的方法,其他几个因素也会很重要:书写速度、鼠标移动、无需单击的导航、同时浏览多个页面等等。Bot 预防软件特别具有主动性。出于安全原因,它应该这样做。

我们不会讨论这部分,但这些操作会给他们新的理由来阻止请求。同样,好公民不会尝试大量登录。我们谈论的是抓取,而不是恶意活动。

有时网站会使数据收集变得更加困难,这可能不是故意的。但是使用现代前端工具,CSS 类可能每天都在变化,从而破坏了精心准备的脚本。有关更多详细信息,请阅读我们之前关于如何在 Python 中抓取数据的条目。

结论

我们希望您记住唾手可得的果实:

  1. IP轮换代理
  2. 具有挑战性的目标的住宅代理
  3. 全套标头,包括用户代理
  4. 当需要 Javascript 挑战时使用 Playwright 绕过机器人检测 – 可能添加隐身模块
  5. 避免可能将您标记为机器人的模式

还有更多,可能还有更多我们没有涵盖。但是使用这些技术,您应该能够大规模地抓取和抓取。毕竟,如果您知道如何操作,则可以在不被 python 阻止的情况下进行网络抓取。

如果您了解更多网站抓取技巧或对应用它们有疑问,请联系我们。

请记住,我们介绍了抓取和避免被阻止,但还有更多内容:抓取、转换和存储内容、扩展基础设施等等。敬请关注!

类似文章