本文将为您提供如何抓取 Google 搜索结果的动手示例。它展示了两种从 Google 搜索中抓取内容的方法:自己做所有事情和使用 SERP API。除非搜索引擎发生重大变化,否则您应该能够通过仅调整几个参数来重现代码示例。让我们开始吧!
Google 搜索 API 怎么样?
什么,现在?啊,对了,Google 搜索 API。从技术上讲,该公司提供了一种从其搜索引擎中提取数据的官方方法。它来来去去,但目前 任何需要 SERP 信息的人都可以使用它。但是,它确实不是这项工作的好工具:
- 该 API 用于在一个网站或一小组网站中进行搜索。您可以将其配置为搜索整个网络,但这需要修补。
- 与可视化界面和网页抓取工具相比,API 提供的信息越来越少。
- API 花费了很多钱:1000 个请求会让你少 5 美元,这是一个白天的抢劫。您可以提出的每日请求数量还有进一步的限制。
总体而言,考虑到 Google Search API 的局限性,您实际上并不想使用它。相信我,通过网络抓取路线,您将既省钱又省心。
构建一个基本的 Web Scraper
本教程的这一部分将向您展示如何构建一个可以从 Google 搜索中提取结果的非常基本的爬虫。它的功能包括下载 HTML 代码、解析页面标题、描述和 URL,以及以 .JSON 格式保存数据。
首先,我们需要导入必要的模块:
本教程的这一部分将向您展示如何构建一个可以从 Google 搜索中提取结果的非常基本的爬虫。它的功能包括下载 HTML 代码、解析页面标题、描述和 URL,以及以 .JSON 格式保存数据。
首先,我们需要导入必要的模块:
import json
from urllib.parse import urlencode
import requests
from lxml import etree
我们需要json因为那将是我们的输出格式。Urlencode将帮助我们在使用 Google 的 URL 参数(更具体地说是 + 符号)时避免编码问题。Requests是用于发出 HTTP 请求的标准 Python 库。而lxml将是我们的解析器;或者,您可以使用Beautiful Soup。
第二步是为我们的查询创建一个函数:
def do_it_yourself_simple():
"""If you had to do it yourself"""
domain = 'com'
payload = {
'q': 'car insurance', # Query.
# You'd need to figure out how to generate this.
'uule': 'w+CAIQICIHR2VybWFueQ', # Location.
}
results = [] # Parsed results.
params = urlencode(payload)
url = f'https://www.google.{domain}/search?{params}'
在这里,我们选择了要从中提取数据的域——在本例中为 .com。然后,我们创建了一个有效载荷,其中包含有关查询(汽车保险)和我们的位置(德国)的信息。位置参数称为uule。您可以使用本指南找到它。
params对象允许我们的查询包含+号。这就是urlencode库发挥作用的地方。没有它,您的函数可能会中断,因为它将查询字符串中的空格显示为 unicode: %20。
url对象由我们的参数组成一个 URL。
结果创建了一个对象来保存我们解析的数据。
完成后,我们需要下载我们的 Google 搜索页面。这很简单:
# Scrape.
response = requests.get(url=url)
现在,我们需要解析我们抓取的数据。首先,让我们从 HTML 创建一个 XML 树:
# Parse.
parser = etree.HTMLParser()
tree = etree.fromstring(
response.text,
parser,
)
然后,让我们找到我们需要的数据。执行此操作的标准方法是检查页面。在 Chrome 上,右键单击 -> 检查。然后,您需要单击目视检查按钮;它将帮助您找到正确的元素
此处的目的是为一个搜索结果找到覆盖整个框的元素。如果您不能这样做,请不要担心:只需向上移动树,直到找到它。
在我们的例子中,元素嵌套在 .div 中,作为一个名为ZINbbc的类。对您来说可能会有所不同,因为 Google 会动态生成类名。我们使用 XPath 语法选择它:
result_elements = tree.xpath(
'.//div['
' contains(@class, "ZINbbc") '
' and not(@style="display:none")'
']'
'['
' descendant::div[@class="kCrYT"] '
' and not(descendant::*[@class="X7NTVe"])' # maps
' and not(descendant::*[contains(@class, "deIvCb")])' # stories
']',
)
请注意,我们在这里又做了两件事。第一个是删除对普通用户隐藏的元素——我们不需要它们。第二个排除了包含来自地图和故事的数据的类,以便我们只抓取自然搜索结果。
下一步是遍历每个相关结果并从中提取我们需要的数据。再次使用 XPath,我们指定我们需要 URL (a.href)、每个结果的页面标题 (h3) 以及它们的元描述(BNeawe类):
for element in result_elements:
results.append(
{
'url': element.xpath('.//a/@href')[0],
'title': element.xpath('.//h3//text()')[0],
'description': element.xpath(
'.//div[contains(@class, "BNeawe")]//text()',
)[-1],
# Other fields you would want.
}
)
最后,我们将刚刚抓取并解析的数据写入 .JSON 文件:
with open('diy_parsed_result_simple.json', 'w+') as f:
f.write(json.dumps(results, indent=2))
看,我们刚刚抓取了查询汽车保险的第一个 Google 搜索结果页面。
添加高级功能
当然,我们的 Google 抓取工具非常基础,如果不进一步改进,我们就无法扩展它。因此,让我们尝试使用一些高级功能对其进行拉皮条。
提醒一句:尽管我们称这些功能为高级功能(与准系统脚本相比,它们确实如此),但仅添加它们不足以支持任何严重的网络抓取操作。认真地刮谷歌需要更多的东西。不过,这是一个很好的起点。
用户代理
用户代理会向网站显示您用于连接的设备和浏览器。这很重要,因为没有它,您的网络抓取工具将非常明显,并且没有人喜欢被抓取。因此,虽然它本身还不够,但真正的用户代理在避免不必要的阻塞方面还有很长的路要走。
在我们的代码中添加用户代理实际上非常简单。这是我们在开始时创建的相同功能。只是这一次在headers对象中随机选择了五个不同的用户代理:
def do_it_yourself_advanced():
"""If you had to do it yourself"""
domain = 'com'
pages = 2
payload = {
'q': 'car insurance', # Query.
# You'd need to figure out how to generate this.
'uule': 'w+CAIQICIHR2VybWFueQ', # Location.
}
headers = {
'User-Agent': random.choice(
[
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.131 Safari/537.36',
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.169 Safari/537.36',
'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:66.0) Gecko/20100101 Firefox/66.0',
'Mozilla/5.0 (Windows NT 10.0; Win 64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.157 Safari/537.36',
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KTML, like Gecko) Chrome/73.0.3683.103 Safari/537.36',
]
)
}
注意:要随机化用户代理,我们需要导入一个名为random的附加模块。
分页
当然,我们不想只抓取Google 搜索结果的一页。重点是什么?所以,我们要添加分页。实际上,这很简单——我们只需要一个名为pages的参数和一个循环:
def do_it_yourself_advanced():
"""If you had to do it yourself"""
domain = 'com'
pages = 2
payload = {
'q': 'car insurance', # Query.
# You'd need to figure out how to generate this.
'uule': 'w+CAIQICIHR2VybWFueQ', # Location.
}
headers = {
'User-Agent': random.choice(
[
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.131 Safari/537.36',
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.169 Safari/537.36',
'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:66.0) Gecko/20100101 Firefox/66.0',
'Mozilla/5.0 (Windows NT 10.0; Win 64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.157 Safari/537.36',
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KTML, like Gecko) Chrome/73.0.3683.103 Safari/537.36',
]
)
}
results = [] # Parsed results.
for count in range(0, pages * 10, 10):
payload['start'] = count # One page is equal to 10 google results.
params = urlencode(payload)
url = f'https://www.google.{domain}/search?{params}'
代理
我们可能需要扩大规模的另一件事是代理。网站对来自同一 IP 地址的许多连接请求不满意,因此最好不时轮换它们。包括代理是一个两行的事情;我们将它们嵌套在分页循环中:
for count in range(0, pages * 10, 10):
payload['start'] = count # One page is equal to 10 google results.
params = urlencode(payload)
url = f'https://www.google.{domain}/search?{params}'
proxies = {
'https': 'http://<username>:<password>@<host>:<port>',
}
请注意,这种方法假设您使用的是旋转代理网络,您不需要自己处理旋转逻辑。
这是完整的代码示例:
from urllib.parse import urlencode
import requests
import random
from lxml import etree
def do_it_yourself_advanced():
"""If you had to do it yourself"""
domain = 'com'
pages = 2
payload = {
'q': 'car insurance', # Query.
# You'd need to figure out how to generate this.
'uule': 'w+CAIQICIHR2VybWFueQ', # Location.
}
headers = {
'User-Agent': random.choice(
[
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.131 Safari/537.36',
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.169 Safari/537.36',
'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:66.0) Gecko/20100101 Firefox/66.0',
'Mozilla/5.0 (Windows NT 10.0; Win 64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.157 Safari/537.36',
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KTML, like Gecko) Chrome/73.0.3683.103 Safari/537.36',
]
)
}
results = [] # Parsed results.
for count in range(0, pages * 10, 10):
payload['start'] = count # One page is equal to 10 google results.
params = urlencode(payload)
url = f'https://www.google.{domain}/search?{params}'
proxies = {
'https': 'http://<username>:<password>@<host>:<port>',
}
# Scrape.
response = requests.get(url=url, proxies=proxies, headers=headers)
# Parse.
parser = etree.HTMLParser()
tree = etree.fromstring(
response.text,
parser,
)
result_elements = tree.xpath(
'.//div['
' contains(@class, "ZINbbc") '
' and not(@style="display:none")'
']'
'['
' descendant::div[@class="kCrYT"] '
' and not(descendant::*[@class="X7NTVe"])' # maps
' and not(descendant::*[contains(@class, "deIvCb")])' # stories
']',
)
for element in result_elements:
results.append(
{
'url': element.xpath('.//a/@href')[0],
'title': element.xpath('.//h3//text()')[0],
'description': element.xpath(
'.//div[contains(@class, "BNeawe")]//text()',
)[-1],
# Other fields you would want.
}
)
with open('diy_parsed_result_advanced.json', 'w+') as f:
f.write(json.dumps(results, indent=2))
这就是第一部分!现在,让我们尝试使用专用工具——SERP API 来做同样的事情。
使用 SERP API
在我们深入研究代码之前——SERP API 到底是什么?简而言之,它就像一个远程网络抓取工具,为您处理大部分抓取逻辑。无需自己构建所有内容,您只需为其提供所需的关键字和参数,然后将数据保存在数据库中。所以,没有解析、没有代理、没有验证码——一切都由 API 处理。
对于此示例,我们将使用一个名为SERPMaster的工具。我们喜欢它,因为它灵活且功能惊人,而且价格低廉。SERPMaster 支持多种检索数据的方法;我们将选择实时,这意味着我们将通过开放连接接收数据。另一种方法,回调,可以让我们通过 webhook 按需收集输出。
为了达到与我们的 DIY 网络爬虫相同的结果,我们需要的所有代码是:
def serpmaster():
"""If you used SERPMaster"""
payload = {
'scraper': 'google_search',
'q': 'car insurance',
'domain': 'com',
'geo': 'Germany',
'parse': 'true',
}
pages = 2
for i in range(1, pages + 1):
payload['page'] = i
response = requests.post(
url='https://rt.serpmaster.com/',
auth=('username', 'password'),
json=payload,
)
with open(f'serpmaster_parsed_result_page_{i}.json', 'w+') as f:
f.write(json.dumps(response.json(), indent=2))
与我们之前的脚本相比,这个脚本效率更高。一个功能包括:
- 带有一些简单参数的有效载荷,
- 一个分页循环,
- 使用登录凭据向 SERPMaster 发出 POST 请求,
- 和一个用于输出的 .JSON 文件。
而已。我们不需要担心形成正确的 URL,我们的解析逻辑封装在一行中,我们可以选择任何我们喜欢的地理位置,并且代理已经内置。这种方法也可以很好地扩展而不需要太多努力。
但是:便利需要花钱,所以这可能会阻止您跳入 SERP API。也就是说,SERPMaster 有免费试用版;您可以尝试数百个请求,看看是否值得。
还有其他方法吗?
还有其他方法可以抓取 Google 搜索吗?是的。替代方案是使用可视化网络爬虫、浏览器扩展或数据收集服务。让我们简要介绍一下。
可视化网络爬虫程序
可视化网络爬虫程序可以让您在没有任何编码经验的情况下从 Google 中提取数据。它们为您提供了一个浏览器窗口,您只需在其中指向并单击要抓取的数据点并以您选择的格式下载它们。最困难的部分是构建一个适当的分页和动作循环工作流程,但与自己编写代码相比,这仍然很容易。
你什么时候应该得到一个可视化的网络爬虫? 当您需要少量中等数量的数据并且没有编码经验时。
使用哪些可视化网络抓取工具? ParseHub 和 Octoparse 是两个不错的选择。我们偏爱 Octoparse,因为它具有更轻的 UI 和预制模板,可用于快速基本抓取。
浏览器扩展
浏览器扩展提供了开始抓取 Google 搜索的最简单方法之一。您需要做的就是将它们添加到浏览器中。之后,该过程非常类似于视觉网络刮板:指向并单击网页的元素并将它们下载到您的计算机。这样的扩展非常强大。他们可以处理 JS、分页,甚至执行表单填充等操作。
什么时候应该使用网页抓取浏览器扩展? 当您需要来自 Google 搜索的快速且不太复杂的数据时。
使用哪些浏览器扩展? 我们喜欢 Web Scraper。它是 Chrome 和 Firefox 的免费扩展,可嵌入到开发人员工具中。另一种选择是用于 Chrome 的 Data Miner。后者更容易使用,并且有数以千计的公共 食谱 (你可以使用预先构建的刮板)。
数据收集服务
数据收集服务是从 Google 搜索中获取数据的最简单方法。您指定您的要求、预算,然后收到所有格式正确的结果以供进一步使用。就是这样。您无需构建或维护刮板,无需担心刮板逻辑,甚至您的刮板项目的法律方面。你唯一担心的是钱。
什么时候应该使用数据收集服务? 这个很简单:当你在运行一个中大型项目时,有资金,没有人为你构建一个网络爬虫。
选择哪种数据收集服务? 不乏提供数据收集服务的公司。一些例子是 ScrapingHub 和 Bright Data。