使用 scrapy 解析项目并提高代码 speed/overall 性能

Using scrapy to parse items and improve code speed/overall performance

我正在做一个项目,每天 webscrape 大量数据并将它们输入数据库。我一直在尝试尽可能有条不紊地做这件事。我能够创建一个能够从多个页面中提取数据的解析函数。但是,当我添加一个解析项函数时,我一直失败。我已经阅读了 Scrapy 文档并尝试尽可能地遵循它但无济于事。

卡了几天,想向Stack Overflow社区求助。如果我的问题真的很长,我深表歉意。我一直很困惑,我想看看我是否能得到一个彻底的 explanation/guidance,而不是对我遇到的每一个问题一个接一个地 posting 问题。我只是想让我的项目尽可能高效和出色地完成。非常感谢任何帮助。谢谢^^

完整代码:

import scrapy
from scrapy import Request
from datetime import datetime
from scrapy.crawler import CrawlerProcess

dt_today = datetime.now().strftime('%Y%m%d')
file_name = dt_today+' HPI Data'

    # Create Spider class
    class UneguiApartments(scrapy.Spider):
        name = 'unegui_apts'
        allowed_domains = ['www.unegui.mn']
        custom_settings = {'FEEDS': {f'{file_name}.csv': {'format': 'csv'}},
                       'USER_AGENT': "Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/27.0.1453.93 Safari/537.36"}
        start_urls = [
        'https://www.unegui.mn/l-hdlh/l-hdlh-zarna/oron-suuts-zarna/'
    ]

    def parse(self, response):
        cards = response.xpath('//li[contains(@class,"announcement-container")]')
        for card in cards:
            name = card.xpath(".//a[@itemprop='name']/@content").extract_first()
            price = card.xpath(".//*[@itemprop='price']/@content").extract_first()
            date_block = card.xpath("normalize-space(.//div[contains(@class,'announcement-block__date')]/text())").extract_first().split(',')
            date = date_block[0].strip()
            city = date_block[1].strip()
            link = card.xpath(".//a[@itemprop='url']/@href").extract_first()

            yield Request(link, callback=self.parse_item)
            yield {'name': name,
                   'price': price,
                   'date': date,
                   'city': city,
                   'link': 'https://www.unegui.mn' + link
                   }

    def parse_item(self, response):
        # extract things from the listinsg item page
        spanlist = response.xpath('.//span[contains(@class, )]//text()').extract()
        alist = response.xpath(".//a[@itemprop='url']/@href").extract()

        list_item1 = spanlist[0].strip()
        list_item2 = spanlist[1].strip()
        list_item3 = alist[0].strip()
        list_item4 = alist[1].strip()

        yield {'item1': list_item1,
                'item2': list_item2,
                'item3': list_item3,
                'item4':list_item4
        }

        next_url = response.xpath(
            "//a[contains(@class,'red')]/parent::li/following-sibling::li/a/@href").extract_first()
        if next_url:
            # go to next page until no more pages
            yield response.follow(next_url, callback=self.parse)

    # main driver
    if __name__ == "__main__":
        process = CrawlerProcess()
        process.crawl(UneguiApartments)
        process.start()

当前结果:

运行 日志: https://pastebin.com/LFiwQWZ6

预期结果: 这些是我添加项目解析函数之前的抓取结果。我想添加我的项目解析并在 href 列表和 spanlist 中抓取另外 13 个值(参考:Item Picture Snippet & HTML snippet)并将其添加到我预期结果的结果中。

项目图片片段:

项目HTML片段:

旁注,如果有人有时间或兴趣进一步指导我。将来我计划添加到我的代码中,这样我就可以:

  1. 使用 selenium 之类的东西能够抓取动态页面(例如,能够点击显示 text/data 的按钮,然后能够抓取它)。
  2. 清理、转换和标准化抓取的数据。
  3. 添加到 DBeaver 数据库。
  4. 检查并删除重复项,使数据库中只有 1 个唯一的公寓列表。
  5. 运行 每天自动安排一次网络抓取。

希望,我想使用 scrapinghttps://www.unegui.mn 中抓取所有类别的数据,并为每个类别制作一个蜘蛛。该网站有大约 94,000 个列表。完成后,理想情况下我想添加更多不同的网站以获得额外的广告列表。

目前,我正在抓取并输出为 CSV 文件。理想情况下,我希望不仅能够根据选择输出为 CSV 文件,而且能够将我的数据直接上传到 DBeaver 数据库。我明白这是一项艰巨的任务,但我真的很想做一个复杂多样的scrapy代码,可以用来每天收集大量数据。是不是野心太大了?再次感谢您的所有意见,对于冗长的 post.

,我深表歉意

问题:

  1. 对一个网站的每个类别使用一个蜘蛛是否更好, 还是每个网站一个蜘蛛?
  2. 我应该将我所有的网络列表抓取代码放在一个项目文件夹中还是将它们分开?
  3. 是否有我应该遵循的命名约定?
  4. 使用 ItemLoader() 和 Input 和 Output 会使我的代码更高效、更快速吗?如果没有,考虑到我未来的补充,我应该实施什么path/method?
  5. 我是否应该遵循任何其他方法或约定来使我的代码尽可能高效和结构良好?

查看您的日志似乎有一个错误:

    raise ValueError(f'Missing scheme in request url: {self._url}')
ValueError: Missing scheme in request url: /adv/5040771_belmonte/

我怀疑这条线有问题,因为它可能会产生对亲属的请求 url Scrapy 的 Request 对象总是期望 url 是绝对的:

        cards = response.xpath('//li[contains(@class,"announcement-container")]')
        for card in cards:
            name = card.xpath(".//a[@itemprop='name']/@content").extract_first()
            price = card.xpath(".//*[@itemprop='price']/@content").extract_first()
            date_block = card.xpath("normalize-space(.//div[contains(@class,'announcement-block__date')]/text())").extract_first().split(',')
            date = date_block[0].strip()
            city = date_block[1].strip()
            link = card.xpath(".//a[@itemprop='url']/@href").extract_first()

            yield Request(link, callback=self.parse_item)
            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
            yield {'name': name,
                   'price': price,
                   'date': date,
                   'city': city,
                   'link': 'https://www.unegui.mn' + link
                   }

这里的link变量可以是相对的link(就像你lgos中提到的/adv/5040771_belmonte.

要解决此问题,请 url明确加入:

yield Request(response.urljoin(link), callback=self.parse_item)

或使用response.follow快捷方式:

yield response.follow(link, callback=self.parse_item)

此外,您应该在回调之间使用项目结转:

    def parse(self, response):
        cards = response.xpath('//li[contains(@class,"announcement-container")]')
        for card in cards:
            # ...
            item = {'name': name,
                   'price': price,
                   'date': date,
                   'city': city,
                   'link': 'https://www.unegui.mn' + link
                   }
            yield response.follow(link, callback=self.parse_item, meta={"item": item})

    def parse_item(self, response):
        # retrieve previously scraped item
        item = response.meta['item']
        # ... parse additional details

        item.update({'item1': list_item1,
                'item2': list_item2,
                'item3': list_item3,
                'item4':list_item4
        })
        yield item
        # ... next url