Skip to main content
 首页 » 编程设计

python之使用 scrapy 从 200k 域中提取文本

2025年05月04日28jirigala

我的问题是:我想从某个域(例如 www.example.com)中提取所有有值(value)的文本。所以我去这个网站并访问最大深度为 2 的所有链接并将其写入 csv 文件。

我用 scrapy 编写了这个模块,它使用 1 个进程并产生多个爬虫来解决这个问题,但它效率低下 - 我能够爬取 ~1k 域/~5k 网站/小时,据我所知我的瓶颈是CPU(因为 GIL?)。离开我的电脑一段时间后,我发现我的网络连接断开了。

当我想使用多个进程时,我刚从扭曲中得到错误:Multiprocessing of Scrapy Spiders in Parallel Processes所以这意味着我必须学习 twisted,与 asyncio 相比,我会说我不赞成,但这只是我的意见。

所以我有几个想法该怎么做

  • 反击并尝试学习 twisted 并使用 Redis 实现多处理和分布式队列,但我不认为 scrapy 是适合此类工作的工具。
  • 使用 pyspider - 它具有我需要的所有功能(我从未使用过)
  • 使用 nutch - 它非常复杂(我从未使用过)
  • 尝试构建我自己的分布式爬虫,但在爬取 4 个网站后,我发现了 4 个极端情况:SSL、重复、超时。但是添加一些修改很容易,例如:集中爬行。

您推荐什么解决方案?

Edit1:共享代码

class ESIndexingPipeline(object): 
    def __init__(self): 
        # self.text = set() 
        self.extracted_type = [] 
        self.text = OrderedSet() 
        import html2text 
        self.h = html2text.HTML2Text() 
        self.h.ignore_links = True 
        self.h.images_to_alt = True 
 
    def process_item(self, item, spider): 
        body = item['body'] 
        body = self.h.handle(str(body, 'utf8')).split('\n') 
 
        first_line = True 
        for piece in body: 
            piece = piece.strip(' \n\t\r') 
            if len(piece) == 0: 
                first_line = True 
            else: 
                e = '' 
                if not self.text.empty() and not first_line and not regex.match(piece): 
                    e = self.text.pop() + ' ' 
                e += piece 
                self.text.add(e) 
                first_line = False 
 
        return item 
 
    def open_spider(self, spider): 
        self.target_id = spider.target_id 
        self.queue = spider.queue 
 
    def close_spider(self, spider): 
        self.text = [e for e in self.text if comprehension_helper(langdetect.detect, e) == 'en'] 
        if spider.write_to_file: 
            self._write_to_file(spider) 
 
    def _write_to_file(self, spider): 
        concat = "\n".join(self.text) 
        self.queue.put([self.target_id, concat]) 

调用:

def execute_crawler_process(targets, write_to_file=True, settings=None, parallel=800, queue=None): 
    if settings is None: 
        settings = DEFAULT_SPIDER_SETTINGS 
 
    # causes that runners work sequentially 
    @defer.inlineCallbacks 
    def crawl(runner): 
        n_crawlers_batch = 0 
        done = 0 
        n = float(len(targets)) 
        for url in targets: 
            #print("target: ", url) 
            n_crawlers_batch += 1 
            r = runner.crawl( 
                TextExtractionSpider, 
                url=url, 
                target_id=url, 
                write_to_file=write_to_file, 
                queue=queue) 
            if n_crawlers_batch == parallel: 
                print('joining') 
                n_crawlers_batch = 0 
                d = runner.join() 
                # todo: print before yield 
                done += n_crawlers_batch 
                yield d  # download rest of data 
        if n_crawlers_batch < parallel: 
            d = runner.join() 
            done += n_crawlers_batch 
            yield d 
 
        reactor.stop() 
 
    def f(): 
        runner = CrawlerProcess(settings) 
        crawl(runner) 
        reactor.run() 
 
    p = Process(target=f) 
    p.start() 

蜘蛛不是特别有趣。

请您参考如下方法:

您可以使用 Scrapy-Redis .它基本上是一个 Scrapy 蜘蛛,它从 Redis 的队列中获取要爬取的 URL。 优点是您可以启动许多并发的蜘蛛,这样您就可以更快地爬行。蜘蛛的所有实例都会从队列中拉取 URL,并在用完要抓取的 URL 时空闲等待。 Scrapy-Redis 的存储库附带一个示例项目来实现这一点。

我使用 Scrapy-Redis 启动了 64 个爬虫实例,在大约 1 小时内抓取了 100 万个 URL。