【问题标题】:How to click a link by text with No Text in Python如何在 Python 中单击无文本的文本链接
【发布时间】:2021-04-11 14:22:33
【问题描述】:

我正在尝试从 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 class="anchor__anchor--2QZvA" href="/weingut-r-a-pfaffl-austrian-cherry-zweigelt/w/1261542?year=2018&amp;price_id=23409078&amp;cart_item_source=direct-explore" target="_blank">

您能否建议如何单击超链接后没有文字的 Selenium 葡萄酒?我在网络搜索期间没有找到正确的答案。提前致谢

【问题讨论】:

    标签: python selenium selenium-webdriver web-scraping


    【解决方案1】:

    你做的工作比你必须做的要多——我是说 Selenium。访问该页面时,我使用 Google Chrome 的开发工具记录了我的网络流量,我看到我的浏览器向 REST API 发出了 HTTP GET 请求,其响应是 JSON 并包含您可能想要的所有葡萄酒/价格信息.因此,您无需进行任何刮擦。只需使用所需的查询字符串参数和正确的标头来模仿该 GET 请求。似乎 REST API 只关心 user-agent 标头,这是微不足道的。


    1. 首先访问URL 在谷歌浏览器中。
    2. F12 键打开 Google Chrome 开发工具菜单。 点击Network 标签。
    3. 单击圆形记录按钮。它应该变红,它会 开始在下面的日志中记录所有网络流量。点击 旁边的过滤按钮,然后点击XHR。这只会 在日志中显示 XHR (XmlHttpRequest) 请求。我们感兴趣 特别是这些请求,因为它是通过 XHR 请求 通常制作 API。它现在应该是这样的:

    1. 在 Chrome 开发工具菜单仍然打开的情况下,右键单击(不是 左键单击)页面刷新按钮以显示下拉菜单。 然后,点击Empty Cache and Hard Reload

    这将清空您浏览器对该页面的缓存,并强制您 浏览器刷新页面。当页面被刷新时,您 应该开始看到一些条目出现在流量日志中。

    现在日志中应该有一些 XHR 请求条目。我们不知道其中哪一个是我们真正感兴趣的,所以我们只是查看所有这些,直到找到一个看起来可能是正确的(或者,如果它包含我们的信息)重新寻找,如个别葡萄酒的信息等)。我碰巧知道我们对以explore?... 开头的那个感兴趣,所以让我们点击它。

    1. 单击条目后,将在右侧打开一个面板 日志。点击Headers 标签。

    此选项卡包含有关如何提出此请求的所有信息。在General 区域下,您可以看到Request URL,这是我们向其发出请求的 REST API 端点的 URL。这个 URL 可能很长,因为它通常还包含查询字符串参数(那些是在 explore? 之后的键值对,例如 country_code=DEcurrency_code=EUR。它们由 &amp; 分隔) .查询字符串参数很重要,因为它们包含有关我们要应用于查询的某些过滤器的信息。在我的代码示例中,我已将它们从 REST API 端点 URL 中删除,而是将它们移动到 params 字典中。此步骤不是必需的 - 您也可以将它们留在 URL 中,但我发现这种方式更易于阅读和修改。查询字符串参数也很重要,因为有时某些 API 会期望某些参数出现在请求中,或者他们会期望它们具有某些值 - 换句话说,某些 API 对其查询字符串参数非常挑剔,如果您以 API 未预料到的方式删除或篡改它们,API 会说您的请求表述不正确。

    General 区域,您还可以看到Request Method,在我们的例子中是GET。这告诉我们,我们的浏览器发出了 HTTP GET 请求。并非所有 API 端点的工作方式都相同,有些需要 HTTP POST 等。

    Status Code 告诉我们服务器发回的状态码。 200 表示一切正常。您可以了解更多关于HTTP status codes here的信息。

    让我们看看Response Headers 区域。该区域包含服务器在发出请求后发回的所有响应标头。这些对于浏览器来说非常有用,例如设置 cookie 或了解如何解释服务器发回的数据。

    Request Headers 区域包含您的浏览器在发出请求时发送到服务器的所有标头。通常,复制所有这些键值对并将它们转换为 Python 字典 headers 是个好主意,因为这样可以确保 Python 脚本会发出与浏览器发出的完全相同的请求。但是,通常,我喜欢尽可能地减少它。我知道许多 API 非常关心 user-agent 字段,所以通常我会保留那个,但有时他们也关心 referer。当您使用不同的 API 时,您必须通过反复试验找出 API 关心的请求标头。这个 API 恰好只关心user-agent

    最后一个区域Query String Parameters 只是在一个人性化的键值对列表中显示来自Request URL 的查询字符串参数的一种可爱方式。有时从此处而不是从 URL 复制它们会很有帮助。

    1. 现在,点击Preview 标签旁边的Headers 标签。

    Preview 选项卡包含一个漂亮打印的预览,显示了作为浏览器请求的结果发回的实际数据。在我们的例子中,它包含服务器发回的 JSON 数据。您可以单击灰色的小三角形来展开或折叠 JSON 结构的某些部分,以显示不同的数据。

    看这个,我可以看出 JSON 响应是一个大字典,它有一个键 explore_vintage,它的值是另一个字典,它有一个键 records,它的值是一个字典列表,其中每个此列表中的字典表示一个葡萄酒对象。展开第一个记录(第 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 条记录/葡萄酒,但是更改 params 查询字符串参数字典中的 page 键值对将产生您想要的任何页面的记录。我目前位于德国,这就是为什么我的 country_codecurrency_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 中。您必须向每个葡萄酒摘要页面发出请求,并从页面中提取酒精含量值,可能使用正则表达式。

    【讨论】:

    • 非常感谢您的回复!我试图从他们的网站访问 vivino api,但找不到。如果我想抓取其他葡萄酒(或任何其他酒精数据库),我应该尝试以下操作:url = www.website.com/api/explore/explore 还是每种情况下的 api 路径都不同?另外,如果有一种方法可以自动化页面键值对,因为我想抓取 10,000 个葡萄酒数据,而且我认为手工操作不会很有趣:) 最后,有没有办法访问额外的数据:例如酒精含量或葡萄酒生产中使用的葡萄类型?提前致谢
    • @Beginn_noo 不客气。对于您遇到的每个 REST API,API URL .../api/explore/explore 都不相同。 REST API 的开发人员可以随意命名 API 端点——而且,并非每个网站都使用 REST API 来填充前端,所以这次你很幸运 :)。对于您的第二个问题,很容易实现自动化。它基本上是一个for循环。循环的每次迭代,您更改params 中的page 参数,然后向API 发出另一个请求以获取该页面的记录,并逐页收集所有记录。
    • @Beginn_noo 对于您的第三个问题,我将不得不再看一下 API 返回的内容。我会告诉你我发现了什么。
    • 太好了,干杯!我想收集尽可能多的特征,希望能在项目中进一步提高机器学习预测的准确性。我现在会尝试自己玩它
    • 抱歉问的有点晚了,但是您是如何检查浏览器创建了 HTTP GET to Rest API 的?我已经浏览了一段时间的教程,但找不到有意义的教程来解释如何从公共 API 获取 json 结果。可以分享有用的资源吗?我还希望您也从 chrome 开发工具中填写参数 keys(国家代码、货币代码等)。当我转到开发工具的 _Network _ 部分时,我不确定如何继续。
    【解决方案2】:

    只是为了回答你的问题,如何选择带有硒的a标签来执行.click()

    links = browser.find_elements_by_css_selector('div.cleanWineCard__cleanWineCard--tzKxV.cleanWineCard__row--CBPRR > a')
    

    使用css selector,您可以找到div标签,其中葡萄酒的图像被a标签包围。

    现在你可以循环 links 执行 .click() 并做任何你想做的事:

    for a in links:
        a.click()
        # grab information
        # sleep a bit
        #...
    

    【讨论】:

    • 很好,感谢您的帮助
    • Welcome to So - 以防万一,您应该尝试使用 api ;) 结构良好的所有信息:'0: {group: "oak", stats: {count: 98 ,得分:13110}} 1:{组:“black_fruit”,统计:{计数:88,得分:11221}} 2:{组:“香料”,统计:{计数:62,得分:3268}} 3: {group: "non_oak", stats: {count: 58, score: 1243}} ... 结构:酸度:1.6542798 computed_structure_count:278 起泡度:空强度:4.087247 甜度:3.0625458 单宁:2.21928 user_structure_count:63`
    【解决方案3】:

    1- 通过美丽的汤你可以做到以下几点。我稍微修改了代码,但你仍然可以使用漂亮的汤部分。

    from bs4 import BeautifulSoup
    import requests
    import time, os
    from selenium import webdriver
    from selenium.webdriver.common.keys import Keys
    
    
    chromedriver = "C:\\Program Files\Google\Chrome\Application\chromedriver" # path to the chromedriver executable
    os.environ["webdriver.chrome.driver"] = chromedriver
    driver = webdriver.Chrome(chromedriver)
    driver.get('https://www.vivino.com/explore?e=eJwFwbEOQDAUBdC_uaNoMN7NZhQLEXmqmiZaUk3x987xkVXRwLtAVcLLy7qE_tiN0Bz6FhcV7M4s0ZkkB86VUZIL9l4kmyjW4ORmbo0nTTPVDxlkGvg%3D&cart_item_source=nav-explore')
    
    #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 = driver.execute_script("window.scrollTo(0, document.body.scrollHeight);var lenOfPage=document.body.scrollHeight;return lenOfPage;")
    
    list= []
    
    match=False
    while(match==False):
        lastCount = lenOfPage
        time.sleep(7)
        lenOfPage = driver.execute_script("window.scrollTo(0, document.body.scrollHeight);var lenOfPage=document.body.scrollHeight;return lenOfPage;")
        match=True
        soup = BeautifulSoup(driver.page_source, 'html.parser')
    
        for a in soup.find_all('a', href=True):
            print(  a['href'] )
    

    它会列出所有的href。

    2- 替代方案:selenium 还支持通过 XPath 获取元素。您可以检查该项目并复制 XPath,然后您就可以使用它了。请查看以下指南。我会帮你的。 https://selenium-python.readthedocs.io/locating-elements.html

    例如以下作品。我从链接https://stackoverflow.com/a/63828196/3756587 获得了这个工作代码。所有功劳归于用户 mamal:https://stackoverflow.com/users/4941102/mamal

        links = driver.find_elements_by_xpath('//*[@href]')
        for i in links:
            print(i.get_attribute('href'))
    

    现在您拥有所有链接。通过一个简单的 for 循环,您可以继续。

    享受吧!

    【讨论】:

      猜你喜欢
      • 2019-02-14
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-11-11
      • 2016-08-03
      相关资源
      最近更新 更多