Scrapy: проверьте MongoDB на наличие дубликатов перед сканированием - PullRequest
0 голосов
/ 30 апреля 2020

Я сканирую все новости с первой страницы более 50 новостных сайтов ежедневно и храню их в базе данных MongoDB. Я использую новостной URL как _id в качестве уникального идентификатора. Некоторые сайты сканируют значительно больше времени, чем другие. Чтобы ускорить процесс сканирования, мне нужно сначала пройти через базу данных go, а затем сканировать только что извлеченные URL.

Я не хочу использовать persistent support, поскольку это не совсем то, что я ищу , Кроме того, как показано ниже, я написал дублирующий фильтр, но он помогает только в одном сеансе сканирования, и данные исчезают после каждого завершения процесса.

Вот как выглядит мой pipeline.py:

class DuplicatesPipeline:

    def __init__(self):
        self.urls_seen = set()

    def process_item(self, item, spider):

        if item['_id'] in self.urls_seen:
            raise DropItem("Duplicate item found: %s" % item)
        else:
            self.urls_seen.add(item['_id'])
            return item


class MongoDBPipeline:

    def __init__(self, mongo_uri, mongo_db):
        self.mongo_uri = mongo_uri
        self.mongo_db = mongo_db

    @classmethod
    def from_crawler(cls, crawler):
        return cls(
            mongo_uri=crawler.settings.get('MONGO_URI'),
            mongo_db=crawler.settings.get('MONGO_DATABASE')
        )

    def open_spider(self, spider):
        self.client = pymongo.MongoClient(self.mongo_uri)
        self.db = self.client[self.mongo_db]

    def close_spider(self, spider):
        self.client.close()

    def process_item(self, item, spider):
        self.db[spider.name].insert_one(dict(item))
        logging.debug("Article added to MongoDB")
        return item

Сначала мне нужно извлечь URL. Затем go через базу данных _id s, чтобы увидеть, существуют ли уже извлеченные URL-адреса, и только после этого я могу начать сканирование.

Есть ли более простой способ сделать это? Если нет, то как я могу это реализовать?

Ответы [ 2 ]

0 голосов
/ 04 мая 2020

Мне удалось проверить базу данных, чтобы получить все ранее просканированные URL-адреса, чтобы предотвратить дублирование и повысить производительность примерно на 50%. Я использовал руководство , написанное Адрианом Ди Паскуале, и получил некоторые идеи. Вот как выглядит мой паук после модификации. Также, как предложено в статье, pipeline.py был слегка изменен.

class BBCSpider(CrawlSpider):
    name = 'bbc'
    allowed_domains = ['www.bbc.com']
    start_urls = [
        'https://www.bbc.com/news/',
        'https://www.bbc.com/news/world/us_and_canada',
    ]

    rules = [Rule(LinkExtractor(allow=('https:\/\/www.bbc.com\/news\/world-us-canada-[0-9]+$'),
                                deny=('https:\/\/www.bbc.com\/news\/av\/.*')),
                  callback='parse_item',
                  process_links='filter_links')]

    @classmethod
    def from_crawler(cls, crawler, *args, **kwargs):
        kwargs['mongo_uri'] = crawler.settings.get("MONGO_URI")
        kwargs['mongo_database'] = crawler.settings.get('MONGO_DATABASE')
        return super(BBCSpider, cls).from_crawler(crawler, *args, **kwargs)

    def __init__(self, mongo_uri=None, mongo_database=None, *args, **kwargs):
        super(BBCSpider, self).__init__(*args, **kwargs)
        self.mongo_provider = MongoProvider(mongo_uri, mongo_database)
        self.collection = self.mongo_provider.get_collection(self)
        # URLs that have already been scraped in previous crawling sessions
        self.scraped_urls = self.collection.find().distinct('_id')

    def filter_links(self, links):
        # Removes URLs that have already been scraped in previous crawling sessions
        for url in self.scraped_urls:
            for link in links:
                if url in str(link):
                    links.remove(link)
        return links

    def parse_item(self, response):
        if response.status == 200:

            item = SmartCrawlerItem()

            item['_id'] = response.url
            item['title'] = response.css('title::text').get()
            item['date'] = response.xpath('//div[@class="story-body"]//ul[@class="mini-info-list"]//div/text()').get()
            item['article'] = response.css('div.story-body__inner>*::text').getall()

            if None in item.values():
                return
            else:
                item['date'] = get_unique_date(item['date'])
                item['article'] = clean_response(item['article'])
                yield item
0 голосов
/ 30 апреля 2020

Хранение всех предыдущих данных в памяти кажется плохой идеей, так как, когда масштаб увеличивается, это вернет вас в задницу.

У вас есть много возможных способов обхода. Мне кажется, что самый простой подход - просто переписать process_item, чтобы проверить наличие дубликатов перед вставкой.

def process_item(self, item, spider):
        exists = self.db[spider.name].find_one({"_id": dict(item)["_id"]})
        if not exists:
            self.db[spider.name].insert_one(dict(item))
            logging.debug("Article added to MongoDB")
        return item
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...