使用 Python 进行网络抓取:构建网络抓取工具的终极指南

Raluca Penciuc,2021 年 3 月 30 日

如果说 20 世纪我们的思维模式是 "时间就是金钱",那么现在我们的思维模式则是 "数据"。更多的数据意味着更多的洞察力,从而做出更好的决策,赚更多的钱。

特别是在过去十年中,网络搜索和网络搜索器的普及率大幅提高。越来越多的企业需要准确的营销策略,这意味着需要在短时间内获得大量信息。

随着数据提取成为新的焦点,企业开始关注如何从中获益。对于开发人员来说,这可能是促进业务发展的好方法,或者只是一个磨练编码技能的好项目。

即使你的工作与网络抓取无关,但你是一个 Python 团队成员,在本文的最后,你将了解到一个新的利基市场,在那里你可以充分发挥你的技能。我们将了解如何使用 Python 构建自己的 Web Scraping。

了解使用 Python 进行网络抓取

但首先,网络搜刮是什么意思?从最基本的层面来说,网络搜刮器是从网站上提取数据,前提是并非所有网站都在公共应用程序接口下提供数据。

如果考虑到掌握的信息越多,就能在业务中做出更好的决策,那么这个过程就比看上去更有用。

如今,网站的内容越来越多,因此完全靠手工来完成这一过程绝非良策。这就是我们要讨论建立一个自动工具来进行刮擦的原因。

"你可能会问:"我需要这些数据做什么?好吧,让我们来看看网络搜刮是救命稻草的一些顶级使用案例:

  • 价格情报:电子商务公司需要竞争对手的价格信息,以便做出更好的定价和营销决策。
  • 市场调研:市场分析意味着高质量、大容量和有洞察力的信息。
  • 房地产:个人或企业需要汇总多个来源的报价。
  • 创造商机:为不断发展的业务寻找客户。
  • 品牌监测:公司将对论坛、社交媒体平台和评论进行分析,以跟踪人们对其品牌的看法。
  • 最低广告价格 (MAP) 监控可确保品牌的在线价格符合其定价政策。
  • 机器学习:开发人员需要提供训练数据,才能让人工智能驱动的解决方案正常运行。

您可以在这里找到更多使用案例和更详细的说明。

"酷,让我们开始吧!"你可能会说。别急。

即使你弄清了网络搜刮的工作原理以及它能如何改善你的业务,要创建一个网络搜刮器也不是那么容易。首先,有些人出于不同的原因不希望在自己的网站上安装刮板。

其中一个原因是,刮擦意味着一秒钟内会发送很多请求,这会使服务器超负荷。网站所有者有时会将此视为黑客攻击(拒绝服务),因此网站会采取阻止机器人的措施来保护自己。

其中一些措施可以是

  • IP 阻断:当网站检测到来自同一 IP 地址的大量请求时,就会发生这种情况;网站可能会完全禁止你访问或大大降低你的访问速度)。
  • CAPTCHAs(用于区分计算机和人类的完全自动化公共图灵测试):对人来说是非常琐碎的逻辑问题,但对程序员来说却是令人头疼的问题。
  • 蜜罐:集成 链接,人类看不见,但机器人看得见;一旦机器人掉入陷阱,网站就会封杀其 IP。
  • 需要登录:网站可能会在登录页面后面隐藏一些您需要的信息;即使您在网站上进行了身份验证,刮擦程序也无法访问您的凭据或浏览器 cookie。
博客图片

有些网站可能没有采用这些技术,但他们希望通过使用 Javascript 来获得更好的用户体验,这一简单的事实就给网络搜刮者的工作增加了难度。

当网站使用 Javascript 或 HTML 生成框架时,只有在与网站进行了某些交互或执行了生成 HTML 文档的脚本(通常用 Javascript 编写)后,才能访问某些内容。

我们还要考虑提取数据的质量。例如,在电子商务网站上,您可能会根据居住地区看到不同的价格。这些数据并不十分准确,因此机器人必须想办法尽可能准确地提取数据。

如果您设法克服了所有这些问题,您仍然需要考虑到网站的结构总是会发生变化。毕竟,网站需要对用户友好,而不是对机器人友好,因此我们的自动化工具必须适应这些变化。

在这场永无休止的刮擦大战中,机器人自己想出了各种解决方案。它们的目标都是尽可能地在互联网上重现人类的行为。

例如,您可以使用 IP 代理服务来避免 IP 屏蔽。最好使用付费服务,因为免费服务会公开自己的 IP,这样网站可能会屏蔽它们。

您还可以集成验证码解码器。它们将帮助您实现连续的数据馈送,但会稍微减慢刮擦过程。

作为蜜罐陷阱的一种解决方案,你可以使用XPath(如果你足够大胆,甚至可以使用正则表达式)来扫描指定项目,而不是整个 HTML 文档。

考虑所有这些问题以及如何克服这些问题可能是一个费时费力的过程。这就是为什么在过去的十年中,网络刮削应用程序接口获得了越来越多的关注。

在 WebScrapingAPI 上,我们从任何网站收集 HTML 内容,应对任何可能的挑战(如前面提到的挑战)。此外,我们使用亚马逊网络服务,因此速度和可扩展性不成问题。您想试试吗?您可以从免费账户开始,每月可调用 1000 次 API。很棒吧?

了解网络

但现在,让我们回到本文的目的上来。我们要学习如何使用 Python 构建网络刮刀。

第一个必须了解的概念是超文本传输协议(HTTP),它解释了服务器和客户端之间的通信。其背后的理念非常简单。客户端(应用程序)向服务器发送信息(HTTP 请求),服务器返回响应。

消息包含多种信息,描述了客户端及其处理数据的方式:方法、HTTP 版本和报头。

在网络搜索中,最常用的 HTTP 请求方法是 GET。这表示你要检索你所请求的数据。如果你想了解更多关于它们的信息,可以在这里找到一份完整而详细的列表。

头信息包括 HTTP 请求或响应的附加信息。我们将讨论网络搜刮中最相关的信息,但您也可以查阅完整列表

  • User-Agent:用于识别应用程序、操作系统、软件及其版本;网络搜刮器使用此标头使请求看起来更真实。
  • Cookie:包含有关请求的状态信息(如身份验证令牌)。
  • Host(主机):指定服务器的域名,并最终指定服务器监听的端口号。
  • Referrer:包含用户来自的源网站;据此,显示的内容可能不同,因此网络搜刮工具也必须考虑到这一点。
  • 接受:通知服务器可以返回什么类型的内容;虽然它可以在客户端和服务器之间提供更有机的交流,但在网络搜刮中经常被忽视。
博客图片

了解 Python

Python 是一种通用的高级编程语言,由于多种原因,它一直受到开发人员的青睐:

  • 可读代码:简单的语法使其成为初学者的最佳选择。
  • 编程范例:面向对象、结构化、函数式和面向方面的编程只是其中几个例子。
  • 强大的标准库:各种模块满足各种需求
  • 活跃的社区:许多开源库和工具

很好,说完这些,让我们准备一下工作区。首先,我们需要 Python3。您可以从这里下载并安装。

本教程与集成开发环境无关,因此您可以随意选择适合自己的集成开发环境。我们更喜欢Visual Studio Code,因为它轻量级且支持多种语言。

我们还将使用大量 Python 库:

  • 请求:提出 HTTP 请求
  • beautifulsoup: 解析 HTML 文档
  • selenium:用于搜索动态内容
  • nltk(可选):用于处理自然语言

您不必事先全部安装,因为每一步都有更多细节和安装说明。

现在,让我们开始搜索网络!

制作自己的网络刮刀

为了方便大家学习,本教程将分成几个步骤。此外,你还会看到一些在数字后标有 .1 的额外步骤。您可能会对这些步骤感兴趣。

第 1 步:检查要搜索的页面

好了,闲话少说,我们开始吧。

首先,我们需要选择一个要抓取的网站。为了说明原理,让我们选择一个简单的教育网站:https://en.wikipedia.org/wiki/Beer。

在这一步中,我们只想检查页面的 HTML 文档,简要了解其结构。在这一步中,使用什么浏览器或操作系统并不重要,过程都是一样的。在图片、链接或简单文本块的任意位置单击右键,然后选择 "检查元素 "选项。

博客图片

元素 "选项卡是我们关注的唯一内容。根据网站的不同,你可以看到大量的 HTML 代码。重要的是,不要被它们淹没,而只检查你感兴趣的数据。

博客图片

步骤 2:发送 HTTP 请求

现在,我们可以开始编写网络刮擦程序的代码了。首先,我们只需向网站发出 HTTP 请求,返回刚才在浏览器中看到的完整 HTML 代码。代码看起来怎么样?嗯,令人惊叹!毕竟我们谈论的是 Python。

让我们安装请求库来执行 HTTP 请求:

pip install requests

现在我们来编写代码:

导入 requests
URL = 'https://en.wikipedia.org/wiki/Beer'
page = requests.get(URL)

很简单,对吧?我们导入了之前安装的库。然后,我们定义了要抓取的网站 URL,并发出了 GET 请求。如果你想看看结果是什么,那就打印出来吧。现在,你看到的只是一个乱七八糟的字符串,它代表了你在浏览器中看到的 HTML。它对我们的帮助不大,因此我们需要对其进行处理。

第 3 步:抓取页面 HTML

为了从结果中提取一些有用的信息,我们将安装beautifulsoup库:

pip install beautifulsoup4

首先,让我们将结果格式化:

import requests
from bs4 import BeautifulSoup

URL = 'https://en.wikipedia.org/wiki/Beer'
page = requests.get(URL)

soup = BeautifulSoup(page.content, 'html.parser')
prettyHTML = soup.prettify()
print(prettyHTML)

我们将前面的结果转换为 BeautifulSoup 对象。通过.content属性,您可以访问 HTML 数据。应用.prettify()方法后,您就可以看到与之前在浏览器中看到的相同格式。

遗憾的是,并不是每个网站都会像这样提供完整的 HTML 代码。如前所述,网络刮擦工具会遇到一些挑战。

步骤 3.1:动态内容

例如,有些页面只有登录后才能看到。即使您通过浏览器进行了身份验证,您的 python 脚本也无法访问这些数据。

另一种常见情况是动态网站。这意味着 GET 请求响应不是 HTML 文档,而是 Javascript 脚本。即使你能在浏览器中看到 HTML,那也是因为浏览器执行了脚本。但在你的代码中,你需要在本地运行脚本来获取 HTML。

不过,让我们来看看实际情况。我们将从一个较早的大学练习中选择一个快速建立网站的简约示例:https://dynamic-website.surge.sh。

博客图片

您可以在浏览器中看到完整的 HTML。在本例中,表格的单元格中包含图片。

import requests
from bs4 import BeautifulSoup

URL = 'https://dynamic-website.surge.sh'
page = requests.get(URL)
soup = BeautifulSoup(page.content, 'html.parser')

file = open('page.txt', mode='w', encoding='utf-8')
file.write(soup.prettify())

现在,让我们提取这些 HTML,这意味着我们将运行与之前相同的代码,但要做一些改动:我们更新了 URL 并打开了一个文本文件来存储我们的结果。我们运行程序并检查 page.txt 文件中的相同表格部分。

博客图片

What is that? Where is the table? Well, it is there, except that there was no one to generate it yet. You can look into the <head> tag of the HTML document to see if there are any scripts used:

博客图片

是的,我们确实使用了脚本。

要解决这个问题,我们需要selenium,这是一个用于网络测试和浏览器活动自动化的库。我们将在无头模式下使用它,这意味着它将像普通浏览器一样执行 Javascript 代码,但没有可见的用户界面。

pip install selenium

在本教程中,我们将使用ChromeDriver为硒鼓配置网络驱动程序。记得复制下载路径!我们将其保存在 C 目录中,但任何位置都可以。

from selenium import webdriver
from selenium.webdriver.chrome.options import Options
from bs4 import BeautifulSoup

CHROMEDRIVER_PATH = "your/path/here/chromedriver_win32/chromedriver"
URL = "https://dynamic-website.surge.sh"

options = Options()
options.headless = True
driver = webdriver.Chrome(CHROMEDRIVER_PATH, options=options)

driver.get(URL)
soup = BeautifulSoup(driver.page_source, 'html.parser')

file = open('page.txt', mode='w', encoding='utf-8')
file.write(soup.prettify())

除了不再使用请求库来发出 HTTP 请求外,其余的流程基本相同。

我们重新运行程序,...

博客图片

......瞧!现在我们有了完整的 HTML。

步骤 4:提取特定部分

好了,让我们回到正题上来。

获得完整的 HTML 代码是一个很好的进步,但这一过程并没有结束。大多数情况下,我们需要网站的特定信息,让我们看看如何提取这些信息。

Let’s start with something small -  the title of the website. You can find it in the <head> section of the HTML, under a <title> tag.

博客图片

我们知道一个网站只有一个标题,因此我们将使用.find()方法。该方法将标签名称作为输入,并返回 HTML 元素,因此如果需要其内容,只需通过.text属性访问即可。此外,我们还将为我们的小 scraper 添加一些结构。

def extract_title(soup):
title = soup.find('title')

#output: <title>Beer - Wikipedia</title>
print('Title element: ', title)

#output: Beer - Wikipedia
print('Title: ', title.text)

def main():
URL = 'https://en.wikipedia.org/wiki/Beer'
page = requests.get(URL)
soup = BeautifulSoup(page.content, 'html.parser')

extract_title(soup)

main()

这里没有火箭科学。BeautifulSoup 是一个功能强大的库,支持多种提取特定数据的模式。您可以通过 HTML 元素的名称、id 和 class 属性获取它们,甚至可以使用CSS 选择器。天马行空

让我们再进一步,比如提取出现不止一次的元素。在这种情况下,我们使用.find_all()方法。唯一不同的是,它返回的是一个元素列表,而不是一个元素。这就是为什么之后我们要遍历它并显示每个元素的属性。例如,我们提取了文章中的所有图片:

def extract_images(soup):
images = soup.find_all('img')
for image in images:
imageAlt = image.get('alt')
imageSrc = image.get('src')
print("ALT: ", imageAlt, "SRC: ", imageSrc)

def main():
URL = 'https://en.wikipedia.org/wiki/Beer'
page = requests.get(URL)
soup = BeautifulSoup(page.content, 'html.parser')

extract_images(soup)

main()

步骤 5:刮擦时传递功能

网络搜刮中常见的一种情况是,解析结果列表很长,包含的信息很杂。

例如,您可能已经注意到,我们以前的图片可能包含也可能不包含alt 属性。

或者想象一下我们从文章中提取所有链接。我们都知道,维基百科的一篇文章有大量链接,而我们可能并不想要一个完整的链接列表。结果会有外部和内部链接、参考文献和引用,因此我们需要将它们分为多个类别。

为了解决这个问题,我们将使用一个 lambda 函数.基本上,lambda 会将结果列表中的每个元素作为参数,并应用我们定义的条件,就像使用过滤器一样。

举个实际例子,假设我们需要提取所有内部链接,访问它们的文章,并对每个链接进行总结。考虑到 Python 的用例之一是人工智能,这个例子可能是获取训练数据的绝佳应用。

首先,我们需要安装NLTK库,因为计算摘要意味着处理人类语言。

pip install -U nltk

当然,还要在我们的代码中导入它:

import re
import nltk
import heapq
# 只需要在第一次执行时下载
# 警告:数据集的大小很大;因此需要时间
nltk.download()

注意:如果您是 macOS 用户,可能会出现 "SSL: certificate verify failed"(SSL:证书验证失败)错误。原因可能是 Python3.6 使用了嵌入式版本的 OpenSSL。您只需打开安装 Python 的位置并运行此文件:

/Your/Path/Here/Python 3.6/Install Certificates.command

正如你所看到的,我们还导入了re库(用于正则表达式的操作)和heapq(堆队列的实现)。

很好,我们已经掌握了开始编写代码所需的一切。让我们从提取内部链接开始。如果你回到浏览器,你会注意到我们感兴趣的元素的一些情况。

博客图片

这些事情是

  • href属性有一个值;
  • href值以"/wiki/"开头;
  • The link’s parent is a <p> tag;

这些特征将帮助我们从所有其他链接中区分出我们需要的链接。

既然我们知道了如何找到链接,那就来看看如何提取链接。

count = 0

def can_doo_summary(tag):
global count
if count > 10: return False

# 如果父级不是段落则拒绝
if not tag.parent.name == 'p': return False

href = tag.get('href')
# 如果 href 未设置,则拒绝
if href is None: return False

# 如果 href 值不是以 /wiki/ 开头,则拒绝
if not href.startswith('/wiki/'): return False

compute_summary(href)
return True


def extract_links(soup):
soup.find_all(lambda tag: tag.name == 'a' and can_doo_summary(tag))

def main():
URL = 'https://en.wikipedia.org/wiki/Beer'
page = requests.get(URL)
soup = BeautifulSoup(page.content, 'html.parser')

extract_links(soup)

main()

好吧,这里发生了什么?通过查看extract_links()函数,我们可以发现,我们传递给.find_all()方法的参数不是标签名称,而是一个 lambda 函数。这意味着我们只从 HTML 文档的所有标记中挑选出符合条件的标记。

如您所见,标签的条件是必须是一个链接,并且被上面定义的can_doo_summary()函数接受。在该函数中,我们会剔除所有与之前观察到的特征不符的内容。此外,我们还使用全局变量将提取链接的数量限制为 10 个。如果您需要所有链接,请移除计数变量。

最后,我们为新发现的链接调用compute_summary()函数。这就是文章的摘要。

def compute_summary(href):
global count
full_link = 'https://en.wikipedia.org' + href
page = requests.get(full_link)
soup = BeautifulSoup(page.content, 'html.parser')

# Concatenate article paragraphs
paragraphs = soup.find_all('p')
article_text = ""
for p in paragraphs:
article_text += p.text

# Removing Square Bracket, extra spaces, special characters and digits
article_text = re.sub(r'\[[0-9]*\]', ' ', article_text)
article_text = re.sub(r'\s+', ' ', article_text)
formatted_article_text = re.sub('[^a-zA-Z]', ' ', article_text)
formatted_article_text = re.sub(r'\s+', ' ', formatted_article_text)

# Converting text to sentences
sentence_list = nltk.sent_tokenize(article_text)

# Find frequency of occurrence of each word
stopwords = nltk.corpus.stopwords.words('english')

word_frequencies = {}
for word in nltk.word_tokenize(formatted_article_text):
if word not in stopwords:
if word not in word_frequencies.keys():
word_frequencies[word] = 1
else:
word_frequencies[word] += 1

maximum_frequency = max(word_frequencies.values())

for word in word_frequencies.keys():
word_frequencies[word] = (word_frequencies[word] / maximum_frequency)

# Calculate the score of each sentence
sentence_scores = {}
for sent in sentence_list:
for word in nltk.word_tokenize(sent.lower()):
if word in word_frequencies.keys():
if len(sent.split(' ')) < 30:
if sent not in sentence_scores.keys():
sentence_scores[sent] = word_frequencies[word]
else:
sentence_scores[sent] += word_frequencies[word]

# Pick top 7 sentences with highest score
summary_sentences = heapq.nlargest(7, sentence_scores, key=sentence_scores.get)
summary = '\n'.join(summary_sentences)
count += 1

长话短说,我们向新发现的 URL 发出 HTTP 请求,并将结果转换为 BeautifulSoup 对象,就像文章开头所做的那样。

要计算摘要,我们要提取文章中的所有段落,并将它们连接在一起。然后,我们删除所有可能干扰计算的特殊字符。

简单来说,总结的方法是计算出出现频率最高的词语,并根据词语的出现频率给每个句子打分。最后,我们选出得分最高的前 7 个句子。

这不是我们这篇文章的主题,但如果你对自然语言处理感到好奇,甚至充满热情,可以在这里了解更多信息。

第 6 步:生成 CSV 以存储数据

进入本指南的最后一步,我们需要查看刮擦结果。到目前为止,我们只是在终端中显示了这些结果,因为只有几行数据。

但现实生活中的刮擦意味着相当大的信息量,因此我们应该看看如何将结果保存到文件中。

让我们使用本地库 csv(因此无需安装其他任何东西)并打开一个名为summaries.csv 的文件。

import csv
summaries_file = open('summaries.csv', mode='a', encoding='utf-8')

如果文件不存在,程序库将创建该文件。此外,我们以 "追加 "模式打开文件,因为每个链接都是按顺序逐个处理的。

    summaries_writer = csv.writer(summaries_file, delimiter=',', quotechar='"', quoting=csv.QUOTE_MINIMAL)
summaries_writer.writerow([full_link, summary])

compute_summary()函数的最后,我们只需初始化写入器并开始添加数据。一行由文章的 URL 和摘要组成。

步骤 6.1:生成 JSON 文件

最近几年流行的另一种数据序列化格式是 JavaScript Object Notation(JSON)。这种格式易于人类阅读,如果要将刮擦的数据传递给 API 或其他应用程序,这种格式也很方便。

在 Python 中,编写 JSON 文件最简单的方法是将数据传递给一个dict对象。

import json

summaries_file = open('summaries.json', mode='a', encoding='utf-8')
data = {}
data['summaries'] = []

我们将使用 JSON 文件的本地库并打开一个新文件,就像之前打开 CSV 文件一样。然后初始化一个空的dict对象和一个包含摘要的空列表。

data['summaries'].append({
'url': full_link,
'summary': summary
})

在 compute_summary() 函数的末尾,也就是我们之前写入 CSV 的地方,现在我们将一个新的dict对象追加到最终列表中。

json.dump(data, summaries_file, indent=4)

最后,在我们的main()函数中,执行extract_links()过程后,我们将最终对象写入文件。缩进参数只会使结果格式化。

结论和替代方案

好了,我们的教程到此结束。希望它能对你有所帮助,并让你对使用 Python 进行网络搜刮有一些深入的了解。

我们了解了它的好处,以及它如何改善您的业务/应用。与此同时,我们还在这些信息与网络搜刮过程中遇到的一些挑战之间取得了平衡。

如果你是一名开发人员,你可能会发现,通过克服所有这些问题,自己构建一个网络搜刮器是一件令人兴奋的事情。这可能是一次很酷的学习经历。

但作为企业主(或需要数据用于现实生活中的大规模应用的人员),您可能希望避免所涉及的成本(时间、金钱、人力)。

在这种情况下,使用专用的 API 就能解决问题。WebScrapingAPI 克服了所有可能的阻塞点:Javascript 渲染、代理、CAPTHAs 等,并提供可定制的功能。此外,如果您还不太确定,请记住,我们有免费计划可供选择,为什么不试试呢?

新闻和更新

订阅我们的时事通讯,了解最新的网络搜索指南和新闻。

We care about the protection of your data. Read our <l>Privacy Policy</l>.Privacy Policy.

相关文章

缩图
网络抓取科学Scrapy 与 Selenium:选择最佳网络抓取工具综合指南

探索 Scrapy 和 Selenium 在网络刮擦方面的深入比较。从大规模数据采集到处理动态内容,了解两者的优缺点和独特功能。了解如何根据项目需求和规模选择最佳框架。

WebscrapingAPI
作者头像
WebscrapingAPI
14 分钟阅读
缩图
使用案例在金融领域利用网络抓取另类数据:投资者综合指南

探索网络搜索在金融领域的变革力量。从产品数据到情感分析,本指南深入介绍了可用于投资决策的各类网络数据。

米赫内亚-奥克塔维安-马诺拉什
作者头像
米赫内亚-奥克塔维安-马诺拉什
13 分钟阅读
缩图
指南Scrapy vs. Beautiful Soup:网页抓取工具综合比较指南

详细比较 Scrapy 和 Beautiful Soup 这两个领先的网络搜刮工具。了解它们的功能、优缺点,并探索如何将它们结合使用以满足各种项目需求。

WebscrapingAPI
作者头像
WebscrapingAPI
10 分钟阅读