Универсальный сканер Scrapy, использующий фреймворк Scrapy

JSON рептилия регулярное выражение Scrapy

С помощью Scrapy мы можем легко завершить написание поискового робота. Однако, если количество просканированных сайтов очень велико, например, при сканировании новостной информации из основных СМИ, несколько пауков могут содержать много повторяющихся кодов.

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

В этом разделе мы рассмотрим реализацию универсального сканера Scrapy.

1. Ползающий паук

Перед внедрением обычного поискового робота нам нужно узнать о CrawlSpider, ссылка на официальную документацию которого: http://scrapy.readthedocs.io/en/latest/topics/spiders.html#crawlspider.

CrawlSpider — это универсальный паук, предоставляемый Scrapy. В Spider мы можем указать некоторые правила сканирования для извлечения страницы, эти правила сканирования представлены специальной структурой данных Rule. Правило содержит конфигурацию извлечения и последующих страниц.Паук будет определять, какие ссылки на текущей странице необходимо продолжать сканировать в соответствии с правилом, и какие методы необходимо использовать для анализа результатов сканирования каких страниц. .

CrawlSpiderунаследовано отSpiderсвоего рода. КромеSpiderВсе методы и свойства класса, он также предоставляет очень важное свойство и метод.

  • rules, который представляет собой атрибут правила сканирования, содержащий один или несколькоRuleсписок объектов. каждыйRuleОпределены действия для сканирования веб-сайтов, и CrawlSpider их прочитает.rulesкаждый изRuleи проанализировано.

  • parse_start_url(), который является переопределяемым методом. когдаstart_urlsЭтот метод вызывается, когда соответствующий запрос получает ответ, он анализирует ответ и должен вернутьItemобъект илиRequestобъект.

Самое главное здесьRuleопределено, его определение и параметры следующие:

class scrapy.contrib.spiders.Rule(link_extractor, callback=None, cb_kwargs=None, follow=None, process_links=None, process_request=None)

Далее будут описаны по очередиRuleпараметр.

  • link_extractor: объект Link Extractor. Через него паук может узнать, какие ссылки извлечь из просканированных страниц. Извлеченная ссылка автоматически сгенерирует запрос. Это также структура данных, обычно используемаяLxmlLinkExtractorОбъект как параметр, его определение и параметры следующие:

    class scrapy.linkextractors.lxmlhtml.LxmlLinkExtractor(allow=(), deny=(), allow_domains=(), deny_domains=(), deny_extensions=None, restrict_xpaths=(), restrict_css=(), tags=('a', 'area'), attrs=('href', ), canonicalize=False, unique=True, process_value=None, strip=True)

    allowпредставляет собой регулярное выражение или список регулярных выражений, который определяет, какие ссылки, извлеченные с текущей страницы, соответствуют требованиям, и только те ссылки, которые соответствуют требованиям, будут обработаны.denyНапротив.allow_domainsОпределено доменное имя, отвечающее требованиям, и только ссылка этого доменного имени будет использована для создания нового запроса, что эквивалентно белому списку доменных имен.deny_domainsНапротив, это эквивалентно черному списку доменных имен.restrict_xpathsОпределяет извлечение ссылок из соответствующей области XPath на текущей странице, и его значением является выражение XPath или список выражений XPath.restrict_cssОпределяет извлечение ссылок из областей на текущей странице, которые соответствуют селекторам CSS и значением которых является селектор CSS или список селекторов CSS. Есть и другие параметры, представляющие метку извлеченной ссылки, необходимость удаления дубликатов, обработка ссылки и т. д., которые используются нечасто. Вы можете обратиться к описанию параметров документа: http://scrapy.readthedocs.io/en/latest/topics/link-extractors.html#module-scrapy.linkextractors.lxmlhtml.

  • callback: То есть функция обратного вызова и ранее определенный Requestcallbackимеют одно и то же значение. каждый раз отlink_extractorЭта функция будет вызываться при получении ссылки в . Функция обратного вызова получаетresponseв качестве первого аргумента и возвращает список, содержащий объекты Item или Request. Обратите внимание, избегайте использованияparse()как функция обратного вызова. так какCrawlSpiderиспользоватьparse()способ реализовать свою логику, еслиparse()метод переопределен,CrawlSpiderне удастся.

  • cb_kwargs: словарь, содержащий аргументы, переданные функции обратного вызова.

  • follow: логическое значение, т.е.TrueилиFalse, что указывает на то, что по правилу изresponseНужно ли отслеживать извлеченную ссылку. еслиcallbackПараметрыNone,followПо умолчанию установлено значениеTrue, иначе по умолчаниюFalse.

  • process_links: указывает функцию обработчика, начиная сlink_extractorЭта функция будет вызываться при получении связанного списка из , и в основном используется для фильтрации.

  • process_request: Это также назначенная функция обработки. Когда каждый запрос извлекается в соответствии с правилом, эта функция вызывается для обработки запроса. Функция должна вернутьRequestилиNone.

Приведенный выше контент является основным использованием основного правила в CrawlSpider. Но этого может быть недостаточно для завершения работы сканера CrawlSpider. Затем мы используем CrawlSpider для реализации примера сканирования новостного веб-сайта, чтобы лучше понять использование Rule.

2. Загрузчик предметов

Мы узнали об использовании правила CrawlSpider для определения логики сканирования страницы, что является частью настраиваемости. Однако Rule не определяет правил извлечения элементов. Для извлечения Item нам нужно использовать другой модуль Item Loader.

Загрузчик элементов предоставляет удобный механизм, помогающий нам легко извлекать элементы. Он предоставляет ряд API-интерфейсов, которые могут анализировать необработанные данные для присвоения значений элементам. Item предоставляет контейнер для хранения очищенных данных, а Item Loader предоставляет механизм для заполнения контейнера. С ним извлечение данных становится более упорядоченным.

API загрузчика элементов выглядит следующим образом:

class scrapy.loader.ItemLoader([item, selector, response, ] **kwargs)

API загрузчика элементов возвращает новый загрузчик элементов для заполнения данного элемента. Если Item не указан, используемый класс создается автоматически.default_item_class. Также проходит вselectorиresponseпараметры для создания экземпляров с помощью селекторов или параметров ответа.

Параметры API загрузчика элементов будут подробно описаны ниже.

  • item:этоItemпредмет, который можно назватьadd_xpath(),add_css()илиadd_value()и другие способы заполненияItemобъект.

  • selector:этоSelectorОбъект, селектор, используемый для извлечения данных заполнения.

  • response:этоResponseОбъект, используемый для создания ответа селектора.

Типичный экземпляр загрузчика элементов выглядит следующим образом:

from scrapy.loader import ItemLoader
from project.items import Product

def parse(self, response):
    loader = ItemLoader(item=Product(), response=response)
    loader.add_xpath('name', '//div[@class="product_name"]')
    loader.add_xpath('name', '//div[@class="product_title"]')
    loader.add_xpath('price', '//p[@id="price"]')
    loader.add_css('stock', 'p#stock]')
    loader.add_value('last_updated', 'today')
    return loader.load_item()

Здесь сначала объявите элемент продукта, используйтеItemиResponseсоздание экземпляра объектаItemLoader,перечислитьadd_xpath()метод извлекает данные из двух разных мест и присваивает ихnameсобственность, повторное использованиеadd_xpath(),add_css(),add_value()и другие методы по очереди присваивают значения разным свойствам и, наконец, вызываютload_item()Метод реализует разбор Item. Этот способ более регулярен, некоторые параметры и правила мы можем извлекать отдельно и оформлять в конфигурационные файлы или хранить в базе данных для достижения настраиваемости.

Кроме того, каждое поле Item Loader содержит процессор ввода (процессор ввода) и процессор вывода (процессор вывода). Процессор ввода извлекает данные, как только он их получает, а результаты процессора ввода собираются и сохраняются в загрузчике элементов, но не присваиваются элементу. Собрав все данные,load_item()метод вызывается для заполнения регенерацииItemобъект. При выполнении вызова сначала будет вызван обработчик вывода для обработки данных, собранных ранее, а затем сохраненных в элементе, таким образом, создается элемент.

Некоторые из встроенных процессоров описаны ниже.

1. Identity

IdentityЭто самый простой процессор, который не выполняет никакой обработки и возвращает исходные данные напрямую.

2. TakeFirst

TakeFirstВозвращает первое ненулевое значение списка, напримерextract_first()Функция, обычно используемая в качестве процессора вывода, выглядит следующим образом:

from scrapy.loader.processors import TakeFirst
processor = TakeFirst()
print(processor(['', 1, 2, 3]))

Результат выглядит следующим образом:

1

Результат, обработанный этим процессором, возвращает первое ненулевое значение.

3. Join

Joinметод эквивалентен строкеjoin()метод, вы можете объединить список в строку, и по умолчанию строка разделена пробелами, как показано ниже:

from scrapy.loader.processors import Join
processor = Join()
print(processor(['one', 'two', 'three']))

Результат выглядит следующим образом:

one two three

Он также может изменить разделитель по умолчанию по параметру, например, изменить на запятую:

from scrapy.loader.processors import Join
processor = Join(',')
print(processor(['one', 'two', 'three']))

Результат запуска следующий:

one,two,three

4. Compose

ComposeПроцессор создается с заданной комбинацией нескольких функций, каждое входное значение передается первой функции, а его выходные данные передаются второй функции и так далее, пока последняя функция не вернет выходные данные всего процессора, как показано ниже. :

from scrapy.loader.processors import Compose
processor = Compose(str.upper, lambda s: s.strip())
print(processor(' hello world'))

Результат запуска следующий:

HELLO WORLD

Здесь мы создаем Compose Processor, передавая строку с начальным пробелом. Compose Processor имеет два параметра: первыйstr.upper, которая переводит все буквы в верхний регистр; вторая — анонимная функция, вызывающаяstrip()метод удаления начальных и конечных пробельных символов.ComposeДва параметра вызываются последовательно, и результирующая строка, наконец, преобразуется в верхний регистр, а начальные пробелы удаляются.

5. MapCompose

иComposeпохожий,MapComposeСписок входных значений можно обрабатывать итеративно следующим образом:

from scrapy.loader.processors import MapCompose
processor = MapCompose(str.upper, lambda s: s.strip())
print(processor(['Hello', 'World', 'Python']))

Результат запуска следующий:

['HELLO', 'WORLD', 'PYTHON']

Обрабатываемый контент представляет собой итерируемый объект,MapComposeОбъект проходится и обрабатывается последовательно.

6. SelectJmes

SelectJmesВы можете запросить JSON, передать ключ и вернуть значение, полученное из запроса. Однако вам необходимо установить библиотеку Jmespath, прежде чем вы сможете ее использовать.Команда выглядит следующим образом:

pip3 install jmespath

После установки Jmespath процессор можно использовать следующим образом:

from scrapy.loader.processors import SelectJmes
proc = SelectJmes('foo')
processor = SelectJmes('foo')
print(processor({'foo': 'bar'}))

Результат запуска следующий:

bar

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

Далее мы используем пример, чтобы понять использование загрузчика элементов.

3. Задачи этого раздела

Давайте возьмем новости технологии CDC в качестве примера, чтобы понять использование CrawlSpider и Item Loader, а затем извлечем их настраиваемую информацию, чтобы добиться настраиваемости. Ссылка на официальный сайт: http://tech.china.com/. Нам нужно просканировать содержание новостей о технологиях, ссылка: http://tech.china.com/articles/, страница показана ниже.

Мы хотим получить информацию о новостях всех страниц в списке новостей, включая заголовок, текст, время, источник и другую информацию.

4. Новые проекты

Сначала создайте новый проект Scrapy с именем scrapyuniversal следующим образом:

scrapy startproject scrapyuniversal

Чтобы создать CrawlSpider, вам нужно сначала разработать шаблон. Сначала мы можем посмотреть, какие шаблоны доступны, команда выглядит следующим образом:

scrapy genspider -l

Результат запуска следующий:

Available templates:
  basic
  crawl
  csvfeed
  xmlfeed

Когда мы создавали Паука раньше, мы использовали первый шаблон по умолчанию.basic. На этот раз для создания CrawlSpider вам нужно использовать второй шаблон.crawl, команда создания выглядит так:

scrapy genspider -t crawl china tech.china.com

После запуска будет создан CrawlSpider со следующим содержимым:

from scrapy.linkextractors import LinkExtractor
from scrapy.spiders import CrawlSpider, Rule

class ChinaSpider(CrawlSpider):
    name = 'china'
    allowed_domains = ['tech.china.com']
    start_urls = ['http://tech.china.com/']

    rules = (
        Rule(LinkExtractor(allow=r'Items/'), callback='parse_item', follow=True),
    )

    def parse_item(self, response):
        i = {}
        #i['domain_id'] = response.xpath('//input[@id="sid"]/@value').extract()
        #i['name'] = response.xpath('//div[@id="name"]').extract()
        #i['description'] = response.xpath('//div[@id="description"]').extract()
        return i

На этот раз сгенерированный контент Spider является еще однимrulesОпределение свойств. Первый параметр правилаLinkExtractor, как указано вышеLxmlLinkExtractor, только с другим именем. В то же время функция обратного вызова по умолчанию больше неparse, ноparse_item.

5. Определите правила

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

Первое местоstart_urlsМодифицированный на стартовую ссылку, код выглядит следующим образом:

start_urls = ['http://tech.china.com/articles/']

После этого Паук ползетstart_urlsкаждая ссылка внутри. Таким образом, первая просканированная здесь страница — это ссылка, которую мы только что определили. После получения ответа паук будет извлекать гиперссылки на странице в соответствии с каждым правилом для создания дальнейших запросов. Затем нам нужно определить правило, чтобы указать, какие ссылки извлекать.

Текущая страница показана ниже.

Это страница списка новостей, и следующим шагом будет извлечение ссылок на детали каждой новости в списке. Здесь вы можете напрямую указать область, где расположены эти ссылки. Глядя на исходный код, все ссылки есть в IDleft_sideвнутри узла, точнее его внутриclassзаcon_itemузла, как показано на следующем рисунке.

Здесь мы можем использоватьLinkExtractorизrestrict_xpathsуказать атрибут, после чего Паук извлечет все гиперссылки из этой области и сгенерирует Запрос. Однако в навигации по каждой статье могут быть и другие теги гиперссылок, и мы просто хотим извлечь нужные ссылки на новости. Настоящий путь ссылки на новость начинается сarticleВначале мы используем регулярное выражение, чтобы сопоставить его и присвоить егоallowпараметры. Кроме того, страницы, соответствующие этим ссылкам, на самом деле являются соответствующими страницами сведений о новостях, и нам нужно проанализировать подробную информацию о новостях, поэтому здесь необходимо указать функцию обратного вызова.callback.

Теперь мы можем построить правило, код выглядит следующим образом:

Rule(LinkExtractor(allow='article\/.*\.html', restrict_xpaths='//div[@id="left_side"]//div[@class="con_item"]'), callback='parse_item')

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

Однако разница между узлом следующей страницы и другими ссылками на страницы невелика.Чтобы удалить эту ссылку, мы можем напрямую использовать метод сопоставления текста XPath, поэтому здесь мы напрямую используемLinkExtractorизrestrict_xpathsатрибут для указания извлеченной ссылки. Кроме того, нам не нужно извлекать детали страницы, соответствующие этой ссылке на пейджинг, например, страницу сведений о новостях, то есть нам не нужно генерировать Item, поэтому нам не нужно добавлятьcallbackпараметр. Кроме того, если страница на следующей странице прошла успешно, ее необходимо продолжить анализировать, как в приведенной выше ситуации, поэтому также необходимо добавитьfollowПараметрыTrue, представитель продолжает следить за анализом соответствия. фактически,followПараметр также можно опустить, т.к.callbackкогда пусто,followПо умолчаниюTrue. Здесь Правило определяется следующим образом:

Rule(LinkExtractor(restrict_xpaths='//div[@id="pageStyle"]//a[contains(., "下一页")]'))

а сейчасrulesстановится:

rules = (
    Rule(LinkExtractor(allow='article\/.*\.html', restrict_xpaths='//div[@id="left_side"]//div[@class="con_item"]'), callback='parse_item'),
    Rule(LinkExtractor(restrict_xpaths='//div[@id="pageStyle"]//a[contains(., "下一页")]'))
)

Затем запускаем код, команда следующая:

scrapy crawl china

Теперь, когда реализованы перелистывание страниц и подробное сканирование страниц, мы реализуем эту функцию только путем определения двух правил, и эффект операции показан на следующем рисунке.

6. Проанализируйте страницу

Следующее, что нам нужно сделать, это проанализировать содержимое страницы и извлечь заголовок, время публикации, текст и источник. Сначала определите Item следующим образом:

from scrapy import Field, Item

class NewsItem(Item):
    title = Field()
    url = Field()
    text = Field()
    datetime = Field()
    source = Field()
    website = Field()

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

Предварительный просмотр страницы сведений показан ниже.

Если вы извлекаете содержимое, как и раньше, просто вызовитеresponseПеременнаяxpath(),css()Дождитесь метода. здесьparse_item()Реализация метода выглядит так:

def parse_item(self, response):
    item = NewsItem()
    item['title'] = response.xpath('//h1[@id="chan_newsTitle"]/text()').extract_first()
    item['url'] = response.url
    item['text'] = ''.join(response.xpath('//div[@id="chan_newsDetail"]//text()').extract()).strip()
    item['datetime'] = response.xpath('//div[@id="chan_newsInfo"]/text()').re_first('(\d+-\d+-\d+\s\d+:\d+:\d+)')
    item['source'] = response.xpath('//div[@id="chan_newsInfo"]/text()').re_first('来源:(.*)').strip()
    item['website'] = '中华网'
    yield item

Таким образом мы извлекаем информацию о каждой новости и формируем объект NewsItem.

На этом этапе мы фактически завершили извлечение предмета. Снова запустите паука следующим образом:

scrapy crawl china

Выходное содержимое показано на рисунке ниже.

Теперь мы можем успешно извлечь информацию о каждой новости.

Однако мы обнаружили, что это извлечение было очень нерегулярным. Далее мы используемItem Loader,пройти черезadd_xpath(),add_css(),add_value()и другие способы извлечения конфигурации. мы можем переписатьparse_item(),Следующее:

def parse_item(self, response):
    loader = ChinaLoader(item=NewsItem(), response=response)
    loader.add_xpath('title', '//h1[@id="chan_newsTitle"]/text()')
    loader.add_value('url', response.url)
    loader.add_xpath('text', '//div[@id="chan_newsDetail"]//text()')
    loader.add_xpath('datetime', '//div[@id="chan_newsInfo"]/text()', re='(\d+-\d+-\d+\s\d+:\d+:\d+)')
    loader.add_xpath('source', '//div[@id="chan_newsInfo"]/text()', re='来源:(.*)')
    loader.add_value('website', '中华网')
    yield loader.load_item()

Здесь мы определяемItemLoaderподкласс, названныйChinaLoader, реализация которого выглядит так:

from scrapy.loader import ItemLoader
from scrapy.loader.processors import TakeFirst, Join, Compose

class NewsLoader(ItemLoader):
    default_output_processor = TakeFirst()

class ChinaLoader(NewsLoader):
    text_out = Compose(Join(), lambda s: s.strip())
    source_out = Compose(Join(), lambda s: s.strip())

ChinaLoaderнаследоватьNewsLoaderкласс, который определяет универсальныйOut ProcessorзаTakeFirst, что эквивалентно ранее определенномуextract_first()функция метода. мы вChinaLoaderопределено вtext_outиsource_outполе. Здесь используется Compose Processor, который имеет два параметра: первый параметрJoinЭто также процессор, который может объединять список в строку, второй параметр — анонимная функция, которая может удалять символы пробела в начале и в конце строки. После этой серии операций мы преобразуем результат извлечения в виде списка в строку, дедупликацию начальных и конечных пробельных символов.

Код запускается повторно, и эффект извлечения точно такой же.

Пока мы реализовали полууниверсальную конфигурацию обходчика.

Семь, извлечение общей конфигурации

Почему сейчас он только полууниверсальный? Если нам нужно расширить другие сайты, нам все равно нужно создать новый CrawlSpider, определить правило этого сайта и реализовать его отдельно.parse_item()метод. Также много повторяющегося кода, например, имена переменных и методов CrawlSpider почти одинаковы. Так можем ли мы использовать код нескольких похожих поисковых роботов, извлекать совершенно разные места и превращать их в настраиваемые файлы?

Конечно. Итак, какие части мы можем извлечь? Все переменные могут быть извлечены, напримерname,allowed_domains,start_urls,rulesЖдать. Эти переменные могут быть назначены при инициализации CrawlSpider. Мы можем создать нового общего паука для достижения этой функции, команда выглядит следующим образом:

scrapy genspider -t crawl universal universal

Этот новый паук называетсяuniversal. Затем мы извлекаем свойства только что написанного паука и настраиваем его в JSON, называем его china.json, помещаем в папку configs и располагаем рядом с папкой пауков.Код выглядит следующим образом:

{
  "spider": "universal",
  "website": "中华网科技",
  "type": "新闻",
  "index": "http://tech.china.com/",
  "settings": {
    "USER_AGENT": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.90 Safari/537.36"
  },
  "start_urls": [
    "http://tech.china.com/articles/"
  ],
  "allowed_domains": [
    "tech.china.com"
  ],
  "rules": "china"
}

первое полеspiderто есть имя паука, вот оноuniversal. Ниже приводится описание сайта, например, название сайта, тип, домашняя страница и т. д. последующийsettingsспецифичен для этого паукаsettingsКонфигурация, если вы хотите переопределить глобальный проект, конфигурацию в settings.py можно настроить для него отдельно. Далее следуют некоторые свойства Spider, такие какstart_urls,allowed_domains,rulesЖдать.rulesЕго также можно определить как отдельный файл rules.py и превратить в файл конфигурации для разделения правил, как показано ниже:

from scrapy.linkextractors import LinkExtractor
from scrapy.spiders import Rule

rules = {
    'china': (
        Rule(LinkExtractor(allow='article\/.*\.html', restrict_xpaths='//div[@id="left_side"]//div[@class="con_item"]'),
             callback='parse_item'),
        Rule(LinkExtractor(restrict_xpaths='//div[@id="pageStyle"]//a[contains(., "下一页")]'))
    )
}

Таким образом, мы извлечем базовую конфигурацию. Если вы хотите запустить краулер, вам нужно только прочитать файл конфигурации и динамически загрузить его в паук. Итак, нам нужно определить метод для чтения этого файла JSON следующим образом:

from os.path import realpath, dirname
import json
def get_config(name):
    path = dirname(realpath(__file__)) + '/configs/' + name + '.json'
    with open(path, 'r', encoding='utf-8') as f:
        return json.loads(f.read())

Определенныйget_config()После метода нам просто нужно передать ему имя файла конфигурации JSON, чтобы получить эту информацию о конфигурации JSON. Затем мы определяем входной файл run.py и помещаем его в корневую директорию проекта, Его функция — запустить Spider, как показано ниже:

import sys
from scrapy.utils.project import get_project_settings
from scrapyuniversal.spiders.universal import UniversalSpider
from scrapyuniversal.utils import get_config
from scrapy.crawler import CrawlerProcess

def run():
    name = sys.argv[1]
    custom_settings = get_config(name)
    # 爬取使用的Spider名称
    spider = custom_settings.get('spider', 'universal')
    project_settings = get_project_settings()
    settings = dict(project_settings.copy())
    # 合并配置
    settings.update(custom_settings.get('settings'))
    process = CrawlerProcess(settings)
    # 启动爬虫
    process.crawl(spider, **{'name': name})
    process.start()

if __name__ == '__main__':
    run()

Текущая записьrun(). Сначала получите параметры командной строки и назначьте их какname,nameЭто имя файла JSON, которое на самом деле является именем целевого веб-сайта для сканирования. Мы сначала используемget_config()метод, передайте имя, чтобы прочитать только что определенный файл конфигурации. Используйте сканированиеspiderимя в конфигурационном файлеsettingsконфигурации, а затем получитьsettingsКонфигурация и глобальный проектsettingsКонфигурации объединены. Создайте новый CrawlerProcess и передайте конфигурацию, используемую для сканирования. перечислитьcrawl()иstart()способ начать сканирование.

существуетuniversal, мы создаем новый__init__()метод инициализации конфигурации, реализация выглядит следующим образом:

from scrapy.linkextractors import LinkExtractor
from scrapy.spiders import CrawlSpider, Rule
from scrapyuniversal.utils import get_config
from scrapyuniversal.rules import rules

class UniversalSpider(CrawlSpider):
    name = 'universal'
    def __init__(self, name, *args, **kwargs):
        config = get_config(name)
        self.config = config
        self.rules = rules.get(config.get('rules'))
        self.start_urls = config.get('start_urls')
        self.allowed_domains = config.get('allowed_domains')
        super(UniversalSpider, self).__init__(*args, **kwargs)

    def parse_item(self, response):
        i = {}
        return i

существует__init__()метод,start_urls,allowed_domains,rulesи другие свойства назначаются. в,rulesСвойство также считывает конфигурацию rules.py, так что базовая конфигурация сканера успешно реализована.

Затем выполните следующую команду, чтобы запустить сканер:

python3 run.py china

Программа сначала прочитает файл конфигурации JSON, назначит пауку некоторые свойства в конфигурации, а затем начнет сканирование. Эффект операции точно такой же, и результат операции показан на следующем рисунке.

Теперь у нас есть возможность настройки основных свойств Паука. Остальную часть синтаксического анализа также необходимо настраивать.Первоначальная функция синтаксического анализа выглядит следующим образом:

def parse_item(self, response):
    loader = ChinaLoader(item=NewsItem(), response=response)
    loader.add_xpath('title', '//h1[@id="chan_newsTitle"]/text()')
    loader.add_value('url', response.url)
    loader.add_xpath('text', '//div[@id="chan_newsDetail"]//text()')
    loader.add_xpath('datetime', '//div[@id="chan_newsInfo"]/text()', re='(\d+-\d+-\d+\s\d+:\d+:\d+)')
    loader.add_xpath('source', '//div[@id="chan_newsInfo"]/text()', re='来源:(.*)')
    loader.add_value('website', '中华网')
    yield loader.load_item()

Нам также необходимо извлечь эти конфигурации. Переменные здесь в основном включают в себя выбор класса Item Loader,ItemДля выбора класса и определения параметров метода Item Loader мы можем добавить следующее в файл JSONitemКонфигурация:

"item": {
  "class": "NewsItem",
  "loader": "ChinaLoader",
  "attrs": {
    "title": [
      {
        "method": "xpath",
        "args": [
          "//h1[@id='chan_newsTitle']/text()"
        ]
      }
    ],
    "url": [
      {
        "method": "attr",
        "args": [
          "url"
        ]
      }
    ],
    "text": [
      {
        "method": "xpath",
        "args": [
          "//div[@id='chan_newsDetail']//text()"
        ]
      }
    ],
    "datetime": [
      {
        "method": "xpath",
        "args": [
          "//div[@id='chan_newsInfo']/text()"
        ],
        "re": "(\\d+-\\d+-\\d+\\s\\d+:\\d+:\\d+)"
      }
    ],
    "source": [
      {
        "method": "xpath",
        "args": [
          "//div[@id='chan_newsInfo']/text()"
        ],
        "re": "来源:(.*)"
      }
    ],
    "website": [
      {
        "method": "value",
        "args": [
          "中华网"
        ]
      }
    ]
  }
}

определено здесьclassиloaderсвойства, которые представляют классы, используемые Item и Item Loader соответственно. Определенныйattrsсвойства для определения правил извлечения для каждого поля, например,titleКаждый определенный термин содержитmethodатрибут, который представляет используемый метод извлечения, напримерxpathТо есть это означает, что Item Loader вызываетсяadd_xpath()метод.argsпараметры, то естьadd_xpath()Второй параметр, выражение XPath. противdatetimeполе, мы также использовали обычное извлечение, поэтому здесь мы также можем определитьreпараметр для передачи регулярного выражения, используемого при извлечении.

Мы также будем динамически загружать эти конфигурации вparse_item()метод. Наконец, самое главное – добитьсяparse_item()метод следующим образом:

 def parse_item(self, response):
    item = self.config.get('item')
    if item:
        cls = eval(item.get('class'))()
        loader = eval(item.get('loader'))(cls, response=response)
        # 动态获取属性配置
        for key, value in item.get('attrs').items():
            for extractor in value:
                if extractor.get('method') == 'xpath':
                    loader.add_xpath(key, *extractor.get('args'), **{'re': extractor.get('re')})
                if extractor.get('method') == 'css':
                    loader.add_css(key, *extractor.get('args'), **{'re': extractor.get('re')})
                if extractor.get('method') == 'value':
                    loader.add_value(key, *extractor.get('args'), **{'re': extractor.get('re')})
                if extractor.get('method') == 'attr':
                    loader.add_value(key, getattr(response, *extractor.get('args')))
        yield loader.load_item()

Здесь сначала получите информацию о конфигурации элемента, а затемclassконфигурации, инициализируйте его, инициализируйте загрузчик элементов и просмотрите свойства элемента для извлечения по очереди. судитьmethodполе, вызовите соответствующий метод обработки для обработки. какmethodзаcss, просто вызовите загрузчик элементовadd_css()метод извлечения. После того, как все конфигурации будут динамически загружены, вызовитеload_item()метод извлечения элемента.

Повторно запустите программу, и результат показан на рисунке ниже.

Результат бега точно такой же.

давайте оглянемся назадstart_urlsКонфигурация. здесьstart_urlsМожно настроить только определенные ссылки. Если таких ссылок 100, 1000, мы не можем перечислить все ссылки, не так ли? При определенных обстоятельствах,start_urlsТакже требуется динамическая конфигурация. мы будемstart_urlsОн делится на два типа, один из которых предназначен для непосредственной настройки списка URL-адресов, а другой - для вызова метода для генерации, они соответственно определяются какstaticиdynamicтип.

в этом примереstart_urlsочевидноstaticтипа, такstart_urlsПерезапись конфигурации выглядит так:

"start_urls": {
  "type": "static",
  "value": [
    "http://tech.china.com/articles/"
  ]
}

еслиstart_urlsОн генерируется динамически, и мы можем вызвать метод для передачи параметров следующим образом:

"start_urls": {
  "type": "dynamic",
  "method": "china",
  "args": [
    5, 10
  ]
}

здесьstart_urlsопределяется какdynamicтип в видеurls_china(), а затем передайте параметры 5 и 10, чтобы создать ссылки для страниц с 5 по 10. Таким образом, нам нужно только реализовать этот метод и создать новый файл urls.py, как показано ниже:

def china(start, end):
    for page in range(start, end + 1):
        yield 'http://tech.china.com/articles/index_' + str(page) + '.html'

Другие сайты могут настроить сами. Например, для некоторых ссылок необходимо использовать временные метки, параметры шифрования и т. д., которые можно реализовать с помощью пользовательских методов.

Далее в Пауке__init__()метод,start_urlsКонфигурация переписывается следующим образом:

from scrapyuniversal import urls

start_urls = config.get('start_urls')
if start_urls:
    if start_urls.get('type') == 'static':
        self.start_urls = start_urls.get('value')
    elif start_urls.get('type') == 'dynamic':
        self.start_urls = list(eval('urls.' + start_urls.get('method'))(*start_urls.get('args', [])))

Судя здесьstart_urlsТипы обрабатываются по-разному, так что мы можем достичьstart_urlsнастроен.

До сих пор настройки Spider, начальные ссылки, свойства и методы извлечения были полностью настраиваемыми.

Таким образом, конфигурация всего проекта включает в себя следующее.

  • spider: указывает имя используемого паука.

  • settings: информация о конфигурации может быть настроена специально для Spider, что переопределит конфигурацию на уровне проекта.

  • start_urls: укажите начальную ссылку для обхода сканером.

  • allowed_domains: сайты, сканирование которых разрешено.

  • rules: Правила сканирования сайта.

  • item: Правила извлечения данных.

Мы внедрили универсальный сканер Scrapy, и каждому сайту нужно только изменить файл JSON, чтобы получить бесплатную конфигурацию.

Восемь, этот код раздела

Кодовый адрес этого раздела: https://github.com/Python3WebSpider/ScrapyUniversal.

9. Заключение

В этом разделе описывается реализация универсального сканера Scrapy. Мы извлекаем все конфигурации, и каждый раз, когда мы добавляем сканер, нам нужно добавить только файл конфигурации JSON. После этого нам нужно только поддерживать эти файлы конфигурации. Если вы хотите более удобного управления, вы можете сохранить правила в базу данных, а затем подключиться к странице визуального управления.


Этот ресурс был впервые опубликован в личном блоге Цуй Цинцай Цзин Ми:Практическое руководство по разработке веб-краулера на Python3 | Цзин Ми

Если вы хотите узнать больше информации о поисковых роботах, обратите внимание на мой личный публичный аккаунт WeChat: Coder of Attack.

WeChat.QQ.com/Day/5 Это радость VE Z…(автоматическое распознавание QR-кода)