【问题标题】:Can't scrape names from next pages using requests无法使用请求从下一页抓取名称
【发布时间】:2020-05-26 12:37:11
【问题描述】:

我正在尝试使用 python 脚本解析从网页中遍历多个页面的名称。通过我目前的尝试,我可以从它的登录页面获取名称。但是,我找不到使用请求和 BeautifulSoup 从下一页获取名称的任何想法。

website link

到目前为止我的尝试:

import requests
from bs4 import BeautifulSoup

url = "https://proximity.niceic.com/mainform.aspx?PostCode=YO95"

with requests.Session() as s:
    r = s.get(url)
    soup = BeautifulSoup(r.text,"lxml")
    for elem in soup.select("table#gvContractors tr:has([id*='_lblName'])"):
        name = elem.select_one("span[id*='_lblName']").get_text(strip=True)
        print(name)

我尝试修改我的脚本以仅从第二页获取内容,以确保它在涉及下一页按钮时正常工作,但不幸的是它仍然从第一页获取数据:

import requests
from bs4 import BeautifulSoup

url = "https://proximity.niceic.com/mainform.aspx?PostCode=YO95"

with requests.Session() as s:
    r = s.get(url)
    soup = BeautifulSoup(r.text,"lxml")
    payload = {i['name']:i.get('value','') for i in soup.select('input[name]')}
    payload['__EVENTARGUMENT'] = 'Page$Next'
    payload.pop('btnClose')
    payload.pop('btnMapClose')
    res = s.post(url,data=payload,headers={
        'User-Agent': 'Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.87 Safari/537.36',
        'X-Requested-With':'XMLHttpRequest',
        'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
        'Referer': 'https://proximity.niceic.com/mainform.aspx?PostCode=YO95',
        })
    sauce = BeautifulSoup(res.text,"lxml")
    for elem in sauce.select("table#gvContractors tr:has([id*='_lblName'])"):
        name = elem.select_one("span[id*='_lblName']").get_text(strip=True)
        print(name)

【问题讨论】:

  • 您可以获取到下一个页面url的链接并发出下一个请求
  • 这是一个不变的网址,所以我怀疑是否有任何下一页网址具有不同的外观@Eternal。
  • 我检查了网站,似乎分页是 javascript 驱动的。您需要找到一种方法来单击下一步按钮。也许使用硒
  • 我使用了 selenium 并取得了成功。开始单击下一页按钮以从下一页获取内容是一个旧消息。但是,我创建这篇文章是为了找到任何使用 requests 模块和 BeautifulSoup 库的解决方案。谢谢。
  • 如果有使用 javascript 和严重依赖 javascript 的网站执行的操作,那么几乎不可能这样做。但是,您可以检查网站并分析网络选项卡以查看正在进行的 ajax 调用。或者,如果幸运的话,您将能够找到 API。

标签: python python-3.x web-scraping beautifulsoup http-post


【解决方案1】:

正在通过带有 __VIEWSTATE 光标的 POST 请求执行导航到下一页。

如何处理请求:

  1. 向首页发出 GET 请求;

  2. 解析所需数据和__VIEWSTATE游标;

  3. 用接收到的光标准备下一页的 POST 请求;

  4. 运行它,解析所有数据和下一页的新光标。

我不会提供任何代码,因为它需要写下几乎所有爬虫的代码。

==== 已添加 ====

您几乎完成了,但是您错过了两件重要的事情。

  1. 必须在第一个 GET 请求时发送标头。如果没有发送标头 - 我们会得到损坏的令牌(很容易在视觉上检测到 - 它们最后没有 ==)

  2. 我们需要将 __ASYNCPOST 添加到我们发送的有效负载中。 (很有意思:它不是布尔True,而是字符串'true')

这里是代码。我删除了 bs4 并添加了 lxml(我不喜欢 bs4,它很慢)。我们确切地知道我们需要发送哪些数据,所以我们只解析几个输入。

import re
import requests
from lxml import etree


def get_nextpage_tokens(response_body):
    """ Parse tokens from XMLHttpRequest response for making next request to next page and create payload """
    try:
        payload = dict()
        payload['ToolkitScriptManager1'] = 'UpdatePanel1|gvContractors'
        payload['__EVENTTARGET'] = 'gvContractors'
        payload['__EVENTARGUMENT'] = 'Page$Next'
        payload['__VIEWSTATEENCRYPTED'] = ''
        payload['__VIEWSTATE'] = re.search(r'__VIEWSTATE\|([^\|]+)', response_body).group(1)
        payload['__VIEWSTATEGENERATOR'] = re.search(r'__VIEWSTATEGENERATOR\|([^\|]+)', response_body).group(1)
        payload['__EVENTVALIDATION'] = re.search(r'__EVENTVALIDATION\|([^\|]+)', response_body).group(1)
        payload['__ASYNCPOST'] = 'true'
        return payload
    except:
        return None


if __name__ == '__main__':
    url = "https://proximity.niceic.com/mainform.aspx?PostCode=YO95"

    headers = {
            'User-Agent': 'Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.87 Safari/537.36',
            'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
            'Referer': 'https://proximity.niceic.com/mainform.aspx?PostCode=YO95',
            }

    with requests.Session() as s:
        page_num = 1
        r = s.get(url, headers=headers)
        parser = etree.HTMLParser()
        tree = etree.fromstring(r.text, parser)

        # Creating payload
        payload = dict()
        payload['ToolkitScriptManager1'] = 'UpdatePanel1|gvContractors'
        payload['__EVENTTARGET'] = 'gvContractors'
        payload['__EVENTARGUMENT'] = 'Page$Next'
        payload['__VIEWSTATE'] = tree.xpath("//input[@name='__VIEWSTATE']/@value")[0]
        payload['__VIEWSTATEENCRYPTED'] = ''
        payload['__VIEWSTATEGENERATOR'] = tree.xpath("//input[@name='__VIEWSTATEGENERATOR']/@value")[0]
        payload['__EVENTVALIDATION'] = tree.xpath("//input[@name='__EVENTVALIDATION']/@value")[0]
        payload['__ASYNCPOST'] = 'true'
        headers['X-Requested-With'] = 'XMLHttpRequest'

        while True:
            page_num += 1
            res = s.post(url, data=payload, headers=headers)

            print(f'page {page_num} data: {res.text}')  # FIXME: Parse data

            payload = get_nextpage_tokens(res.text)  # Creating payload for next page
            if not payload:
                # Break if we got no tokens - maybe it was last page (it must be checked)
                break

重要

响应不是格式良好的 HTML。所以你必须处理它:切割表或其他东西。祝你好运!

【讨论】:

  • 您更新了您的问题并且几乎完成了。我对您的代码进行了一些更改,并将其作为更新添加到我的答案中。有一些问题你必须处理
  • 这是我不经常遇到的事情。这是一个很棒的方法。不过,我对此有一个小问题。我仍然没有找到任何逻辑如何进入下一页,因为您知道Page$Next 无论我遍历多少页,部分总是相同的。任何建议将不胜感激。谢谢..
  • 您需要在每次请求后从响应中解析令牌,因为您需要将实际光标发送到服务器。我已经更新了代码。我通过正则表达式解析它们。但是如果你在解析表格时“反序列化”响应 - 可能你可以在没有正则表达式的情况下做到这一点。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-07-16
  • 2021-07-21
  • 1970-01-01
  • 2015-06-24
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多