Сложите ссылки второго уровня рекурсивно в Scrapy

Используя Scrapy, я пытаюсь очистить сеть ссылок из Википедии на всех языках. Каждая страница Википедии должна содержать ссылку на элемент Wikidata, который однозначно определяет тему страницы на всех языках. Процесс, который я пытаюсь реализовать, выглядит следующим образом:

  1. Сначала извлеките ссылку на Викиданные с каждой страницы (ссылка "источник").
  2. Перебирайте оставшиеся ссылки на странице.
  3. Для каждой ссылки отправьте запрос на соответствующую страницу ("целевая" ссылка) с новой функцией обратного вызова.
  4. Извлеките ссылку Wikidata из соответствующей целевой страницы.
  5. Переберите все ссылки на целевой странице и перезвоните на оригинал parse функция.

По сути, я хочу пропустить промежуточную ссылку на данной исходной странице и вместо этого получить соответствующую ссылку в Викиданных.

Вот (полуработающий) код, который у меня есть:

from urllib.parse import urljoin, urlparse
from scrapy import Spider    
from wiki_network.items import WikiNetworkItem

WD = \
    "//a/@href[contains(., 'wikidata.org/wiki/Special:EntityPage') \
    and not(contains(., '#'))][1]"

TARGETS = \
    "//a/@href[contains(., '/wiki/') \
    and not(contains(., 'wikidata')) \
    and not(contains(., 'wikimedia'))]"

class WikiNetworkSpider(Spider):
    name = "wiki_network"
    allowed_domains = ["wikipedia.org"]    
    start_urls = ["https://gl.wikipedia.org/wiki/Jacques_Derrida"]
    filter = re.compile(r"^.*(?!.*:[^_]).*wiki.*")               

    def parse(self, response):        

        # Extract the Wikidata link from the "source" page
        source = response.xpath(WD).extract_first()       

        # Extract the set of links from the "source" page                         
        targets = response.xpath(TARGETS).extract()                
        if source:                
            source_title = response.xpath("//h1/text()").extract_first()                                      
            for target in targets:   
                if self.filter.match(str(target)) is not None:
                    item = WikiNetworkItem()        
                    item["source"] = source
                    item["source_domain"] = urlparse(response.url).netloc
                    item["refer"] = response.url
                    item["source_title"] = source_title  

                    # Yield a request to the target page
                    yield Request(url=urljoin(response.url, str(target)), \
                                  callback=self.parse_wikidata, \
                                  meta={"item": item})

    def parse_wikidata(self, response):
        item = WikiNetworkItem(response.meta["item"])
        wikidata_target = response.xpath(WD).extract_first()
        if wikidata_target:      

            # Return current item
            yield self.item_helper(item, wikidata_target, response)

            # Harvest next set of links            
            for s in response.xpath(TARGETS).extract():
                if self.filter.match(str(s)) is not None:
                    yield Request(url=urljoin(response.url, str(s)), \
                                  callback=self.parse, meta={"item": item})

    def item_helper(self, item, wikidata, response):
        print()
        print("Target: ", wikidata)        
        print()
        if item["source"] != wikidata:                
            target_title = response.xpath("//h1/text()").extract_first()                            
            item["target"] = wikidata
            item["target_title"] = target_title
            item["target_domain"] = urlparse(response.url).netloc
            item["target_wiki"] = response.url                                    
            print()
            print("Target: ", target_title)            
            print()
            return item 

Паук запускает и очищает ссылки на некоторое время (количество очищенных элементов обычно достигает 620 или около того), но в конечном итоге он создает огромную очередь, вообще прекращает очистку и просто продолжает ползать. Стоит ли ожидать, что в какой-то момент он снова начнет царапаться?

Кажется, что в Scrapy должен быть простой способ сделать этот вид очистки второго уровня, но другие вопросы, которые я читал до сих пор, в основном касаются обработки пейджинга в Scrapy, а не того, как "свернуть" "ссылка таким образом.

1 ответ

Решение

Пока у вашего паука нет проблем, вы действительно хотите, чтобы при запуске

yield Request(url=urljoin(response.url, str(target)), \
                                  callback=self.parse_wikidata, \
                                  meta={"item": item})

Должно yield раньше, чем очереди запросов ниже типа

yield Request(url=urljoin(response.url, str(s)), \
                                  callback=self.parse, meta={"item": item})

Если вы посмотрите на документацию

https://doc.scrapy.org/en/latest/topics/request-response.html

priority (int) - приоритет этого запроса (по умолчанию 0). Приоритет используется планировщиком для определения порядка, используемого для обработки запросов. Запросы с более высоким значением приоритета будут выполняться раньше. Отрицательные значения допускаются для указания относительно низкого приоритета.

Так что вы будете использовать

yield Request(url=urljoin(response.url, str(target)), \
                                  callback=self.parse_wikidata, \
                                  meta={"item": item}, priority=1)

а также

yield Request(url=urljoin(response.url, str(s)), \
                  callback=self.parse, meta={"item": item}, priority=-1)

Это будет гарантировать, что скребок отдает приоритет ссылкам, что приведет к тому, что данные будут сначала удалены

Другие вопросы по тегам