【问题标题】:Difficulty scraping a table from a webpage难以从网页中抓取表格
【发布时间】:2022-01-24 06:35:35
【问题描述】:

我一直在尝试使用 requests 模块从 webpage 获取表格内容。但是,当我执行下面的脚本时,除了该表中可用的结果之外,我得到了一些乱码:

import requests
from bs4 import BeautifulSoup

link = 'https://cityofdavid.secure.force.com/?productId=01t0600000AE2dR&lang=en_us'

with requests.Session() as s:
    s.headers['User-Agent'] = 'Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.150 Safari/537.36'
    resp = s.get(link)
    soup = BeautifulSoup(resp.text,"lxml")
    for item in soup.select("table.table > tbody > tr"):
        data = [i.text for i in item.select("td")]
        print(data)

当前结果:

["{{timeForTour.TourStartTime__c | date: 'dd/MM/yyyy' : '200'}}", '{{timeForTour.TourStartHour__c }}', '{{timeForTour.EndTime__c}}', '{{timeForTour.Number_Of_Participants_Left__c}}', '\nSelect\n\n']
['{{price.Name}}', '{{price.Field1__c}}', '\n\n\n', '{{(price.quantity)? price.Field1__c*price.quantity:0}}', 'NIS']
['{{item.Name}}', '{{item.Field1__c}}', '{{item.quantity}}', '{{item.quantity*item.Field1__c}}', 'NIS']

预期结果(截断):

24/01/2022  10:00   11:00   15
24/01/2022  12:00   13:00   15
24/01/2022  14:00   15:00   15
24/01/2022  16:00   17:00   15

PS 这是landing page,我单击book a tour 按钮以获取上述请求中的链接。

【问题讨论】:

    标签: python python-3.x web-scraping beautifulsoup python-requests


    【解决方案1】:

    您的问题是最常见的BeautifulSoup 问题,但实际上并没有一个好的答案 - 您的页面是使用 JavaScript 更新的,您需要使用 selenium 或类似的东西来完成网页加载和在抓取之前执行 JavaScript。

    s.get(link) 检索到的 HTML 包含 JavaScript 动态填充表格所需的所有信息,但还没有您要抓取的实际数据。

    由于requests 无法为您运行JavaScript,您需要将其加载到实际的浏览器中,例如由浏览器驱动程序通过selenium 驱动的无头浏览器。

    您可以选择任何风格的浏览器,但例如,要让它与 Firefox 一起使用,您可以从 https://github.com/mozilla/geckodriver/releases 下载 gecko-driver 并将其放入您的项目中(例如放在 bin 文件夹中)。同时安装seleniumpip install selenium(假设你使用pip

    您的代码将是:

    from os import environ
    from selenium import webdriver
    from selenium.webdriver.firefox.options import Options
    from selenium.webdriver.common.by import By
    from selenium.webdriver.support.ui import WebDriverWait
    from selenium.webdriver.support import expected_conditions as ec
    from bs4 import BeautifulSoup
    
    # selenium needs the driver to be on the path, so adding that location
    environ['path'] = f'bin;{environ["path"]}'
    
    # set headless to False if you want to see what happens, 
    # but to True for operation in the background
    options = Options()
    options.headless = True
    
    # this expects `geckodriver.exe` to be on the path
    driver = webdriver.Firefox(options=options)
    # start loading the page
    driver.get('https://cityofdavid.secure.force.com/?productId=01t0600000AE2dR&lang=en_us')
    
    # make sure to keep waiting, until an element appears with the class `timer-message`
    # note that this is different for each page, and may change - 
    # just look for something that doesn't appear on the page until it is done loading
    # or, if there is nothing like it, use some sort of delay like `time.sleep`
    WebDriverWait(driver, 10).until(ec.presence_of_element_located((By.CLASS_NAME, 'timer-message')))
    
    # the below uses `driver.page_source` to stay close to your code
    soup = BeautifulSoup(driver.page_source, "lxml")
    for item in soup.select("table.table > tbody > tr"):
        data = [i.text for i in item.select("td")]
        print(data)
    

    请注意,driver 有自己的方法来访问实时页面中的元素,这可能比使用 bs4 更好 - 请查看文档 https://selenium-python.readthedocs.io/locating-elements.html

    如果您更喜欢完全自动化的解决方案并且不想捆绑二进制文件,您可以使用像 webdriver-manager 这样的库来自动化安装过程。

    请注意,您可能会找到更简单的解决方案,建议您执行以下操作:

    driver = webdriver.Firefox(executable_path='bin/geckodriver.exe')
    driver.get('http://google.com')
    

    这仍然有效,但selenium 告诉您这种做事方式已被弃用,所以不要指望它会无限期地工作。

    最后一句警告:大多数网站不喜欢他们的内容被抓取。在某些情况下,动态加载他们的内容是他们通过默默无闻获得安全性的方式,上面允许您规避这一点 - 确保您正在做的事情实际上是好的。

    一种更轻量级的方法(站点管理员可能会也可能不会)是找出 JS 加载数据的位置并直接加载。以您的页面为例,URL https://cityofdavid.secure.force.com/apexremote 将仅将数据加载为 JSON,但需要您提供正确的标头和 cookie,这可能需要一些时间才能正确处理(并且可能需要您点击无论如何,首先是实际网页,以获取会话 cookie)。 @ManishShetty 的建议答案提供了一些关于如何开始的见解。如果您只是在此处点击此链接,您将被拒绝访问。

    最好的方法始终是检查 API 并在可用时使用它。如果不是,请遵循网站的规则或违反这些规则,后果自负。如果您的 IP 被列入黑名单或更糟,请不要怪我。

    【讨论】:

      【解决方案2】:

      您不会在页面资源中获取该数据,因为它是动态加载的。如果您查看资源页面,您会看到没有您需要的数据。

      您可以从 API 调用中以 JSON 对象的形式获取所需的数据。复制 curl 命令并将其导入 Postman 并在脚本中使用代码。

      在下面的屏幕截图中,您可以看到您想要的实际数据。

      已编辑这将给出预期的结果。

      import requests
      import json
      
      url = "https://cityofdavid.secure.force.com/apexremote"
      
      payload = json.dumps([
        {
          "action": "OrderTourController",
          "method": "getAllTimeForTour",
          "data": [
            "01t0600000AE2dR",
            "2022-1-24 12:18:4",
            0,
            "en_us"
          ],
          "type": "rpc",
          "tid": 3,
          "ctx": {
            "csrf": "VmpFPSxNakF5TWkwd01TMHlOMVF3TmpvME9Eb3dNeTQ1TlRaYSxJZExXMldWSU1neGFlWDdFb01YNi1ULE1XTmpaV1F4",
            "vid": "06624000003gGO2",
            "ns": "",
            "ver": 35
          }
        }
      ])
      headers = {
        'Content-Type': 'application/json',
        'Accept': '*/*',
        'Referer': 'https://cityofdavid.secure.force.com/?productId=01t0600000AE2dR&lang=en_us&_ga=2.113284295.1937905009.1643006618-1494711041.1643006618',
      }
      
      response = requests.request("POST", url, headers=headers, data=payload)
      
      
      
      
      json_result=json.loads(json.loads(response.text)[0]['result'])
      for res in json_result:
          print(res['TourStartTime__c'], res['TourStartHour__c'] ,res['EndTime__c'],res['MaxNumerOfParticipants__c'])
      

      输出

      2022-01-25T08:00:00.000+0000 10:00 11:00 15
      2022-01-25T10:00:00.000+0000 12:00 13:00 15
      2022-01-25T12:00:00.000+0000 14:00 15:00 15
      2022-01-25T14:00:00.000+0000 16:00 17:00 15
      2022-01-26T08:00:00.000+0000 10:00 11:00 15
      2022-01-26T10:00:00.000+0000 12:00 13:00 15
      2022-01-26T12:00:00.000+0000 14:00 15:00 15
      2022-01-26T14:00:00.000+0000 16:00 17:00 15
      2022-01-27T08:00:00.000+0000 10:00 11:00 15
      2022-01-27T10:00:00.000+0000 12:00 13:00 15
      2022-01-27T12:00:00.000+0000 14:00 15:00 15
      2022-01-27T14:00:00.000+0000 16:00 17:00 15
      2022-01-30T08:00:00.000+0000 10:00 11:00 15
      2022-01-30T10:00:00.000+0000 12:00 13:00 15
      2022-01-30T12:00:00.000+0000 14:00 15:00 15
      2022-01-30T14:00:00.000+0000 16:00 17:00 15
      2022-01-31T08:00:00.000+0000 10:00 11:00 15
      2022-01-31T10:00:00.000+0000 12:00 13:00 15
      2022-01-31T12:00:00.000+0000 14:00 15:00 15
      2022-01-31T14:00:00.000+0000 16:00 17:00 15
      

      【讨论】:

      • 虽然这是获取数据的一种方式,但它并不能真正回答问题,当然也不能提供自动抓取数据的方法,因为很多手动交互是需要吗?
      • 您可以在脚本中使用相同的请求来获取数据并进一步处理。使用硒肯定会增加时间。以这种方式获取数据更容易。如果您不知道如何使用它,请询问。
      • @manishshetty,你是在向我讲话还是在向 OP 讲话?我看不出手动使用浏览器和第三方工具会比使用selenium 的脚本中概述的全自动过程更快或更容易地使过程更快或更容易 - 完整的运行只需要大约 2-3 秒,所以我看不到你是怎么打败它的?当然,单独调用 API 会更快,但这是假设 curl URL 中的会话信息会随着时间的推移保持有效,这似乎不太可能。
      • 在这种情况下,它只需要在 Headers 中保持不变的“Referer”。使用的第三方工具只是为了演示该过程。如果他只需要这些数据,他甚至不需要加载页面。此外,如果他想在其他系统中运行刮板,他必须在那里设置 selenium。
      【解决方案3】:

      您正在获取乱码数据,因为内容是由 Javascript 生成的 ,要处理这个问题,您需要使用 selenium 或任何类似的东西来获取 Javascript 数据,而不是请求,您可以使用 selenium 来获取页面源,然后像以前一样继续使用汤

      import time
      
      from bs4 import BeautifulSoup
      from selenium import webdriver
      from webdriver_manager.microsoft import EdgeChromiumDriverManager
      
      driver = webdriver.Edge(EdgeChromiumDriverManager().install())
      
      driver.get('https://cityofdavid.secure.force.com/?productId=01t0600000AE2dR&lang=en_us')
      time.sleep(5)
      htmlSource = driver.page_source
      soup = BeautifulSoup(htmlSource, "lxml")
      for item in soup.select("table.table > tbody > tr"):
          data = [i.text for i in item.select("td")]
          print(data)
      

      将返回您可以根据需要使用的以下数据

      ['24/01/2022', '10:00', '11:00', '15', '\nSelect\n\n']
      ['24/01/2022', '12:00', '13:00', '15', '\nSelect\n\n']
      ['24/01/2022', '14:00', '15:00', '15', '\nSelect\n\n']
      ['24/01/2022', '16:00', '17:00', '15', '\nSelect\n\n']
      ['25/01/2022', '10:00', '11:00', '11', '\nSelect\n\n']
      ['25/01/2022', '12:00', '13:00', '15', '\nSelect\n\n']
      ['25/01/2022', '14:00', '15:00', '15', '\nSelect\n\n']
      ['25/01/2022', '16:00', '17:00', '15', '\nSelect\n\n']
      ['26/01/2022', '10:00', '11:00', '14', '\nSelect\n\n']
      ['26/01/2022', '12:00', '13:00', '15', '\nSelect\n\n']
      ['26/01/2022', '14:00', '15:00', '15', '\nSelect\n\n']
      ['26/01/2022', '16:00', '17:00', '15', '\nSelect\n\n']
      ['27/01/2022', '10:00', '11:00', '12', '\nSelect\n\n']
      ['27/01/2022', '12:00', '13:00', '11', '\nSelect\n\n']
      ['27/01/2022', '14:00', '15:00', '15', '\nSelect\n\n']
      ['27/01/2022', '16:00', '17:00', '15', '\nSelect\n\n']
      ['30/01/2022', '10:00', '11:00', '15', '\nSelect\n\n']
      ['30/01/2022', '12:00', '13:00', '15', '\nSelect\n\n']
      ['30/01/2022', '14:00', '15:00', '15', '\nSelect\n\n']
      ['30/01/2022', '16:00', '17:00', '15', '\nSelect\n\n']
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2014-05-21
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2018-12-15
        • 2021-07-25
        • 2016-10-19
        相关资源
        最近更新 更多