Извлечение текста из 200 тысяч доменов с помощью scrapy
Моя проблема: я хочу извлечь весь ценный текст из некоторого домена, например www.example.com. Поэтому я захожу на этот сайт и посещаю все ссылки с максимальной глубиной 2 и записываю в него csv файл.
Я написал модуль в scrapy, который решает эту проблему, используя 1 процесс и получая несколько сканеров, но он неэффективен - я могу сканировать ~1k доменов /~5k веб-сайтов / ч, и, насколько я вижу, мое узкое место - процессор (потому что из GIL?). Оставив компьютер на некоторое время, я обнаружил, что мое сетевое соединение было разорвано.
Когда я хотел использовать несколько процессов, я только что получил ошибку от Twisted: многопроцессорность Scrapy Spiders в параллельных процессах. Таким образом, это означает, что я должен выучить Twisted, который, я бы сказал, не рекомендуется по сравнению с Asyncio, но это только мое мнение.
Так что у меня есть пара идей, что делать
- Отбивайтесь и пытайтесь выучить витую и внедрить многопроцессорность и распределенную очередь с Redis, но я не считаю, что скрапинг - это правильный инструмент для такого рода работы.
- Перейти с 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()
Паук не особо интересен.
1 ответ
Вы можете использовать Scrapy-Redis. Это в основном паук Scrapy, который выбирает URL для сканирования из очереди в Redis. Преимущество состоит в том, что вы можете запустить много одновременных пауков, чтобы вы могли ползти быстрее. Все экземпляры паука извлекают URL-адреса из очереди и ждут простоя, когда им не хватает URL для сканирования. Хранилище Scrapy-Redis поставляется с примером проекта для реализации этого.
Я использую Scrapy-Redis для запуска 64 экземпляров моего сканера, чтобы очистить 1 миллион URL-адресов за 1 час.