【问题标题】:Can't print three fields at the same time within a separate method无法在单独的方法中同时打印三个字段
【发布时间】:2021-04-09 11:59:42
【问题描述】:

我创建了一个蜘蛛来从网站上获取不同项目的名称、描述和状态。蜘蛛可以从一个地方抓取名称和描述,但它必须到同一站点内的另一个地方才能获取状态。

以下是代表如何手动完成整个事情的步骤,这也有助于理解脚本的构建逻辑。

  1. 导航到this link 并从here 解析idslinks connected to ids
  2. 使用ids 生成所需状态可用的json 响应。
  3. 使用links connected to ids 解析内页的名称和描述,如this one

只要蜘蛛以两种不同的方法fetch_status()fetch_content() 打印所需的信息,它就可以正常工作。

到目前为止,我已经尝试过:

import json
import scrapy
import urllib
from bs4 import BeautifulSoup

class OmronSpider(scrapy.Spider):
    name = 'omron'
    start_urls = ['https://industrial.omron.de/de/products/nyb']
    status_url = 'https://industrial.omron.de/en/api/product_lifecycle_management/search?'

    headers = {
        'User-Agent': 'Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.114 Safari/537.36',
        'Accept': '*/*'
    }

    def parse(self, response):
        soup = BeautifulSoup(response.text,"lxml")
        for item in soup.select("table.details > tbody > tr.filtered > td.product-name a"):
            product_id = item.get_text(strip=True)

            product_url = response.urljoin(item.get("href"))
            yield scrapy.Request(product_url,headers=self.headers,callback=self.fetch_content)

            params = {'q': product_id}
            req_url = f'{self.status_url}{urllib.parse.urlencode(params)}'
            yield scrapy.Request(req_url,headers=self.headers,callback=self.fetch_status)

    def fetch_status(self, response):
        item = json.loads(response.text)['data']
        if item:
            yield {"status":item[0]['status']}
        else:
            yield {"status":None}

    def fetch_content(self, response):
        soup = BeautifulSoup(response.text,"lxml")
        product_name = soup.select_one("header.page-width > h1").get_text(strip=True)
        description = soup.select_one(".content > p").get_text(strip=True)
        yield {"product_name":product_name,"description":description}

我怎样才能在一个单独的方法中同时打印product_namedescriptionstatus 中的三个字段? p>

【问题讨论】:

  • parse 是获取您正在寻找的所有结果的方法吗?如果是这样,如果您希望将结果放在一起,为什么要yield 2 次单独的时间?另外,您确定要使用yield 而不是return
  • 为什么不将接收到的数据全部存储在一个变量中(例如dictlist)和yield 那个变量?
  • 我使用了两次yield,因为我向不同的 url 发送了两个不同的请求。我并不是说我希望坚持我已经应用的逻辑。鉴于我已准备好采用任何更好的方法来取得成果。
  • 你有一个 url 的例子来让 json 工作吗?当我尝试任何查询时,我只会收到 404 响应
  • 除非您使用此标头 'Accept': '*/*' 或类似的东西,否则您将始终获得 404 状态。

标签: python python-3.x web-scraping scrapy


【解决方案1】:

您可以使用cb_kwargs 将数据从一个回调传递到另一个回调。特别针对您的情况,您希望将已解析的数据(product_namedescription)传递给您的 fetch_status 方法。

下面是一个示例(在测试中我看到 status 总是 None 所以不确定是否需要进一步调试)。

我改变了一些额外的东西:

  • 使用内置的TextResponse.css 方法来选择内容,而不是使用BeautifulSoup(如果您感觉更舒服,可以使用bs4,但scrapy 有您在此示例中需要的内容)
  • 使用Response.follow 而不是手动创建新的Requests(有一些优点,例如可以直接使用Selector,就像我在parse 方法中所做的那样)
  • 使用TextResponse.json 反序列化json(只是一个快捷方式,而不是导入json 模块`)
import scrapy
import urllib


class OmronSpider(scrapy.Spider):
    name = "omron"
    start_urls = ["https://industrial.omron.de/de/products/nyb"]
    status_url = (
        "https://industrial.omron.de/en/api/product_lifecycle_management/search?"
    )

    headers = {
        "User-Agent": "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.114 Safari/537.36",
        "Accept": "*/*",
    }

    def parse(self, response):
        for item in response.css("td.product-name a"):
            yield response.follow(item, callback=self.fetch_content)

    def fetch_content(self, response):
        parsed_data = {
            "product_name": response.css("header.page-width > h1::text").get(),
            "description": response.css(".content > p::text").get(),
        }

        params = {"q": parsed_data["product_name"]}
        yield response.follow(
            f"{self.status_url}{urllib.parse.urlencode(params)}",
            callback=self.fetch_status,
            cb_kwargs=dict(parsed_data=parsed_data),
            headers=self.headers,
        )

    def fetch_status(self, response, parsed_data):
        item = response.json()["data"]
        status = item[0]["status"] if item else None
        yield {**parsed_data, "status": status}

【讨论】:

  • 这真是太棒了@tomjn。我计划不使用这个inline_requestsasync,而你帮助我避免了这种情况。
猜你喜欢
  • 2019-01-08
  • 1970-01-01
  • 2019-09-27
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-02-22
相关资源
最近更新 更多