Сложите ссылки второго уровня рекурсивно в Scrapy
Используя Scrapy, я пытаюсь очистить сеть ссылок из Википедии на всех языках. Каждая страница Википедии должна содержать ссылку на элемент Wikidata, который однозначно определяет тему страницы на всех языках. Процесс, который я пытаюсь реализовать, выглядит следующим образом:
- Сначала извлеките ссылку на Викиданные с каждой страницы (ссылка "источник").
- Перебирайте оставшиеся ссылки на странице.
- Для каждой ссылки отправьте запрос на соответствующую страницу ("целевая" ссылка) с новой функцией обратного вызова.
- Извлеките ссылку Wikidata из соответствующей целевой страницы.
- Переберите все ссылки на целевой странице и перезвоните на оригинал
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)
Это будет гарантировать, что скребок отдает приоритет ссылкам, что приведет к тому, что данные будут сначала удалены