如何在Python中单击无文本的文本链接
我正在尝试从 vivino.com 抓取 Wine 数据并使用 selenium 使其自动化并抓取尽可能多的数据。我的代码如下所示:
import time
from selenium import webdriver
browser = webdriver.Chrome('C:Program Files (x86)chromedriver.exe')
browser.get('https://www.vivino.com/explore?e=eJwFwbEOQDAUBdC_uaNoMN7NZhQLEXmqmiZaUk3x987xkVXRwLtAVcLLy7qE_tiN0Bz6FhcV7M4s0ZkkB86VUZIL9l4kmyjW4ORmbo0nTTPVDxlkGvg%3D&cart_item_source=nav-explore') # Vivino Website with 5 wines for now (simple example). Plan to scrape around 10,000 wines
lenOfPage = browser.execute_script("window.scrollTo(0, document.body.scrollHeight);var lenOfPage=document.body.scrollHeight;return lenOfPage;")
match=False
while(match==False):
lastCount = lenOfPage
time.sleep(7)
lenOfPage = browser.execute_script("window.scrollTo(0, document.body.scrollHeight);var lenOfPage=document.body.scrollHeight;return lenOfPage;")
if lastCount==lenOfPage:
match=True
这将打开一个包含 5 种葡萄酒的网站并向下滚动。现在我想一一点击葡萄酒的超链接以获取有关其价格、酿酒葡萄种类等的信息。因此,基本上我的脚本将尝试向下滚动以允许在页面上显示尽可能多的葡萄酒,然后单击第一个超链接,获取附加信息并返回。然后,该过程将重复。我不认为这是一个有效的策略,但这就是我目前想到的。
我的问题是 vivino 网站中的超链接。href 链接附近没有文本,它允许我使用find_element_by_link_text函数:
<a href="/weingut-r-a-pfaffl-austrian-cherry-zweigelt/w/1261542?year=2018&price_id=23409078&cart_item_source=direct-explore" target="_blank">
您能否建议如何单击超链接后没有文本的带有硒的葡萄酒?我在网络搜索期间没有找到正确的答案。提前致谢
回答
你做的工作比你必须做的要多 - 我的意思是使用 Selenium。访问该页面时,我使用 Google Chrome 的开发工具记录了我的网络流量,我看到我的浏览器向 REST API 发出了 HTTP GET 请求,其响应是 JSON 并包含您可能想要的所有酒/价格信息. 所以,你不需要做任何刮擦。只需使用所需的查询字符串参数和正确的标头模仿该 GET 请求。REST API 似乎只关心user-agent
标题,这是微不足道的。
- 首先,访问
Google Chrome 中的URL。 - 按
F12
键打开 Google Chrome 开发工具菜单。单击Network
选项卡。 - 单击圆形记录按钮。它应该变成红色,它会开始在下面的日志中记录所有网络流量。单击它旁边的过滤器按钮,然后单击
XHR
。这只会在日志中显示 XHR (XmlHttpRequest) 请求。我们对这些请求特别感兴趣,因为通常是通过 XHR 向 API 发出请求。这是它现在的样子:
- 在 Chrome 开发工具菜单仍然打开的情况下,右键单击(而不是左键单击)页面刷新按钮以显示下拉菜单。然后,单击
Empty Cache and Hard Reload
。
这将清空此页面的浏览器缓存,并强制浏览器刷新页面。随着页面的刷新,您应该开始看到流量日志中出现一些条目。
现在日志中应该有一些 XHR 请求条目。我们不知道其中哪一个是我们真正感兴趣的,所以我们只查看所有这些,直到我们找到一个看起来可能是正确的(或者,如果它包含我们的信息)重新寻找,例如个别葡萄酒的信息等)。我碰巧知道我们对以 开头的那个感兴趣explore?...
,所以让我们点击它。
- 单击该条目后,将在日志右侧打开一个面板。单击
Headers
选项卡。
此选项卡包含有关如何发出此请求的所有信息。在该General
区域下,您可以看到Request URL
,这是我们向其发出请求的 REST API 端点的 URL。这个 URL 可能很长,因为它通常还会包含查询字符串参数(那些是在 之后的键值对explore?
,比如country_code=DE
或currency_code=EUR
。它们用 分隔&
)。查询字符串参数很重要,因为它们包含有关我们要应用于查询的某些过滤器的信息。在我的代码示例中,我已将它们从 REST API 端点 URL 中删除,而是将它们移到params
字典。这一步不是必需的 - 您也可以将它们留在 URL 中,但我发现这样更容易阅读和修改。查询字符串参数也很重要,因为有时某些 API 会希望请求中出现某些参数,或者他们希望它们具有某些值 - 换句话说,某些 API 对其查询字符串参数非常挑剔,如果您以 API 不期望的方式删除或篡改它们,API 会说您的请求未正确表述。
在该General
区域中,您还可以看到Request Method
,在我们的例子中是GET
。这告诉我们,我们的浏览器发出了一个 HTTP GET 请求。并非所有 API 端点的工作方式都相同,有些需要 HTTP POST 等。
Status Code
告诉我们服务器发回的状态代码。200
意味着一切顺利。您可以在此处了解有关HTTP 状态代码的更多信息。
让我们来看看该Response Headers
地区。该区域包含服务器在发出请求后发回的所有响应头。这些对于浏览器来说很有用,比如设置 cookie 或了解如何解释服务器发回的数据。
该Request Headers
区域包含您的浏览器在发出请求时发送到服务器的所有标头。通常,复制所有这些键值对并将它们转换为 Python 字典是个好主意headers
,因为这样您就可以确保您的 Python 脚本将发出与浏览器发出的完全相同的请求。然而,通常,我喜欢尽可能地减少它。我知道很多 API 都非常关心这个user-agent
领域,所以通常我会保留那个,但有时他们也关心referer
. 当您使用不同的 API 时,您必须通过反复试验确定 API 关心哪些请求标头。这个 API 恰好只关心user-agent
.
最后一个区域Query String Parameters
只是Request URL
在一个人性化的键值对列表中显示查询字符串参数的一种可爱方式。有时从这里复制它们而不是从 URL 复制它们会很有帮助。
- 现在,单击
Preview
选项卡旁边的Headers
选项卡。
该Preview
选项卡包含作为浏览器请求的结果发回的实际数据的漂亮打印预览。在我们的例子中,这包含服务器发回的 JSON 数据。您可以单击灰色的小三角形来展开或折叠 JSON 结构的某些部分,以显示不同的数据。
看这个,我可以看出 JSON 响应是一个大字典,它有一个键explore_vintage
,它的值是另一个字典,它有一个键,records
它的值是一个字典列表,其中这个列表中的每个字典代表一个 wine 对象。展开第一条记录(第 0 条记录)会显示有关列表中第一款酒的所有信息。您可以随心所欲地探索这些结构,看看有哪些类型的信息可供您使用。
def main():
import requests
url = "https://www.vivino.com/api/explore/explore"
params = {
"country_code": "DE",
"currency_code": "EUR",
"grape_filter": "varietal",
"min_rating": "3.5",
"order_by": "ratings_average",
"order": "desc",
"page": "1",
"price_range_max": "30",
"price_range_min": "7",
"wine_type_ids[]": "1"
}
headers = {
"user-agent": "Mozilla/5.0"
}
response = requests.get(url, params=params, headers=headers)
response.raise_for_status()
records = response.json()["explore_vintage"]["records"]
for record in records:
name = record["vintage"]["name"]
price = record["price"]["amount"]
currency = record["price"]["currency"]["code"]
print(f""{name}" - Price: {price} {currency}")
return 0
if __name__ == "__main__":
import sys
sys.exit(main())
输出:
"Varvaglione Cosimo Varvaglione Collezione Privata Primitivo di Manduria 2015" - Price: 21.9 EUR
"Masseria Borgo dei Trulli Mirea Primitivo di Manduria 2019" - Price: 19.9 EUR
"Vigneti del Salento Vigne Vecchie Primitivo di Manduria 2016" - Price: 22.95 EUR
"Vigneti del Salento Vigne Vecchie Leggenda Primitivo di Manduria 2016" - Price: 17.87 EUR
"Varvaglione Papale Linea Oro Primitivo di Manduria 2016" - Price: 18.85 EUR
"Caballo Loco Grand Cru Apalta 2014" - Price: 27.9 EUR
"Luccarelli Il Bacca Old Vine Primitivo di Manduria 2016" - Price: 20.9 EUR
"Mottura Stilio Primitivo di Manduria 2018" - Price: 12.89 EUR
"Caballo Loco Grand Cru Maipo 2015" - Price: 24.81 EUR
"Lorusso Michele Solone Primitivo 2017" - Price: 21.39 EUR
"Château Purcari Negru de Purcari 2017" - Price: 29.8 EUR
"San Marzano 60 Sessantanni Limited Edition Old Vines Primitivo di Manduria 2016" - Price: 22.85 EUR
"San Marzano 60 Sessantanni Old Vines Primitivo di Manduria 2016" - Price: 20.9 EUR
"San Marzano 60 Sessantanni Old Vines Primitivo di Manduria 2017" - Price: 17.775 EUR
"Lenotti Amarone della Valpolicella Classico 2015" - Price: 27.95 EUR
"Zeni Cruino Rosso Veronese 2015" - Price: 22.9 EUR
"Masseria Pietrosa Palmenti Primitivo di Manduria Vigne Vecchie 2016" - Price: 25 EUR
"Ravazzi Prezioso 2016" - Price: 29.95 EUR
"Nino Negri Sfursat Carlo Negri 2017" - Price: 23.89 EUR
"Quinta do Paral Reserva Tinto 2017" - Price: 29.24 EUR
"Wildekrans Barrel Select Reserve Pinotage 2016" - Price: 29.9 EUR
"Caballo Loco Grand Cru Limarí 2016" - Price: 27.9 EUR
"San Marzano F Negroamaro 2018" - Price: 16.9 EUR
"Atlan & Artisan 8 Vents Mallorca 2018" - Price: 19 EUR
"Schneider Rooi Olifant Red 2017" - Price: 19.5 EUR
>>>
它似乎每页抓取 25 条记录/葡萄酒,但更改查询字符串参数字典page
中的键值对params
将产生您想要的任何页面的记录。我目前位于德国,这就是为什么我的country_code
和currency_code
是"DE"
和"EUR"
,但您应该能够更改它们以满足您的需求。
编辑 - 这里有一些您可能感兴趣的键值对,但我建议您熟悉浏览器的开发工具的工作方式,以便您可以自己在 JSON 中发现这些字段:
record["vintage"]["year"]
record["vintage"]["wine"]["region"]["name"]
record["vintage"]["wine"]["region"]["country"]["name"]
record["vintage"]["wine"]["taste"]["structure"]["acidity"]
record["vintage"]["wine"]["taste"]["structure"]["intensity"]
record["vintage"]["wine"]["taste"]["structure"]["sweetness"]
record["vintage"]["wine"]["taste"]["structure"]["tannin"]
record["vintage"]["wine"]["style"]["grapes"][0]["name"] # 0th grape information
record["vintage"]["wine"]["winery"]["name"]
不幸的是,JSON 不包含酒精含量。此信息硬编码在给定葡萄酒摘要页面的 HTML 中。您必须向每个葡萄酒摘要页面提出请求,并将酒精含量值从页面中提取出来,可能使用正则表达式。