Предыдущие статьи представили некоторые техники и шаблоны для создания крупных, масштабируемых и (самое главное!) поддерживаемых веб-скрейперов. Хотя это можно сделать вручную достаточно легко, многие библиотеки, фреймворки и даже инструменты с графическим интерфейсом попробуют сделать это за вас или по крайней мере попытаются облегчить вашу жизнь.
Этот раздел знакомит с одним из лучших фреймворков для разработки скрейперов: Scrapy. Одна из проблем написания веб-скрейперов заключается в том, что вы часто выполняете одни и те же задачи снова и снова: находите все ссылки на странице, оцениваете разницу между внутренними и внешними ссылками, переходите на новые страницы. Эти базовые шаблоны полезно знать и уметь писать с нуля, но библиотека Scrapy обрабатывает многие из этих деталей за вас.
Конечно, Scrapy не читает мысли. Вам все равно нужно определить шаблоны страниц, указать ему места для начала скрапинга и определить шаблоны URL-адресов для страниц, которые вы ищете. Но в этих случаях он предоставляет чистую структуру для организации вашего кода.
Установка Scrapy
Scrapy предлагает инструмент для загрузки с его веб-сайта, а также инструкции по установке Scrapy с помощью сторонних менеджеров установки, таких как pip. Из-за его относительно большого размера и сложности Scrapy обычно не является фреймворком, который можно установить традиционным способом с помощью
pip install Scrapy
Обратите внимание, что я говорю «обычно», потому что, хотя это теоретически возможно, я обычно сталкиваюсь с одной или несколькими трудными проблемами зависимостей, несоответствиями версий и неразрешимыми ошибками. Если вы настроены установить Scrapy из pip, рекомендуется использовать виртуальное окружение .
Метод установки, который я предпочитаю, — это через менеджер пакетов Anaconda. Anaconda — это продукт, разработанный компанией Continuum, предназначенный для уменьшения трения при поиске и установке популярных пакетов Python для анализа данных.
После установки Anaconda вы можете установить Scrapy, используя эту команду:
conda install -c conda-forge scrapy
Если у вас возникли проблемы или вам нужна актуальная информация, ознакомьтесь с руководством по установке Scrapy для получения дополнительной информации.
Инициализация нового паука
После установки фреймворка Scrapy необходимо выполнить небольшую настройку для каждого паука. Паук — это проект Scrapy, который, подобно своему именитому именнику из мира пауков, предназначен для обхода веб-страниц. На протяжении этой главы я использую термин «паук», чтобы описать конкретный проект Scrapy, и «веб-сканер» для обозначения «любой обобщенной программы, которая обходит веб, используя Scrapy или нет». Чтобы создать нового паука в текущем каталоге, выполните следующую команду из командной строки:
$ scrapy startproject wikiSpider
Это создает новый подкаталог в каталоге, в котором был создан проект, с названием wikiSpider. Внутри этого каталога находится следующая структура файлов:
- scrapy.cfg
- wikiSpider
- — spiders
- — init.py
- — items.py
- — middlewares.py
- — pipelines.py
- — settings.py
- — __init.py__
Эти файлы Python инициализированы заглушками кода, чтобы обеспечить быстрое создание нового проекта паука. Каждый раздел в этой главе работает с проектом wikiSpider.
Написание простого скрейпера
Для создания веб-сканера вы добавите новый файл в каталоге spiders по пути wikiSpider/wikiSpider/spiders/article.py. В созданном файле article.py напишите следующее:
import scrapy
class ArticleSpider(scrapy.Spider):
name = 'article'
def start_requests(self):
urls = [
'http://en.wikipedia.org/wiki/Python_'
'%28programming_language%29',
'https://en.wikipedia.org/wiki/Functional_programming',
'https://en.wikipedia.org/wiki/Monty_Python'
]
return [scrapy.Request(url=url, callback=self.parse) for url in urls]
def parse(self, response):
url = response.url
title = response.css('h1::text').extract_first()
print('URL is: {}'.format(url))
print('Title is: {}'.format(title))
Название этого класса (ArticleSpider) отличается от названия каталога (wikiSpider), что указывает на то, что этот класс отвечает только за сканирование страниц статей в более широкой категории wikiSpider, которую вы позже можете использовать для поиска других типов страниц.
Для крупных сайтов с множеством типов контента вы можете иметь отдельные элементы Scrapy для каждого типа (блоги, пресс-релизы, статьи и т. д.), каждый с разными полями, но все работающие в рамках одного проекта Scrapy. Название каждого паука должно быть уникальным в рамках проекта.
Другие ключевые моменты этого паука — это две функции start_requests и parse.
start_requests — это точка входа, определенная в Scrapy, используемая для генерации объектов Request, которые Scrapy использует для сканирования веб-сайта.
parse — это функция обратного вызова, определенная пользователем, и передается объекту Request с параметром callback=self.parse. Позже вы рассмотрите более мощные вещи, которые можно сделать с функцией parse, но пока она просто выводит заголовок страницы.
Вы можете запустить этот паук для статей, перейдя в каталог wikiSpider/wikiSpider и запустив:
$ scrapy runspider article.py
Стандартный вывод Scrapy довольно подробен. Вместе с отладочной информацией он должен вывести строки, подобные следующим:
2018-01-21 23:28:57 [scrapy.core.engine] DEBUG: Crawled (200)
Пауки с правилами
Паук в предыдущем разделе не является настоящим веб-сканером, ограничиваясь только сбором списка URL-адресов, предоставленных ему. Он не имеет способности самостоятельно искать новые страницы. Чтобы превратить его в полноценный веб-сканер, вам нужно использовать класс CrawlSpider, предоставленный Scrapy.
Этот класс можно найти в файле articles.py в репозитории GitHub:
from scrapy.contrib.linkextractors import LinkExtractor
from scrapy.contrib.spiders import CrawlSpider, Rule
class ArticleSpider(CrawlSpider):
name = 'articles'
allowed_domains = ['wikipedia.org']
start_urls = ['https://en.wikipedia.org/wiki/Benevolent_dictator_for_life']
rules = [Rule(LinkExtractor(allow=r'.*'), callback='parse_items', follow=True)]
def parse_items(self, response):
url = response.url
title = response.css('h1::text').extract_first()
text = response.xpath('//div[@id="mw-content-text"]//text()').extract()
lastUpdated = response.css('li#footer-info-lastmod::text').extract_first()
lastUpdated = lastUpdated.replace('This page was last edited on ', '')
print('URL is: {}'.format(url))
print('Title is: {}'.format(title))
print('Text is: {}'.format(text))
print('Last updated: {}'.format(lastUpdated))
Этот новый класс ArticleSpider расширяет класс CrawlSpider. Вместо того, чтобы предоставлять функцию start_requests, он предоставляет список start_urls и allowed_domains. Это указывает пауку, откуда начинать сканирование и следовать ли или игнорировать ссылку на основе домена.
Также предоставляется список правил. Они предоставляют дополнительные инструкции о том, какие ссылки следует следовать или игнорировать (в данном случае вы разрешаете все URL с регулярным выражением .*).
Помимо извлечения заголовка и URL на каждой странице, были добавлены несколько новых элементов. Текстовое содержимое каждой страницы извлекается с использованием селектора XPath. XPath часто используется при извлечении текстового содержимого, включая текст в дочерних тегах (например, тег <a> внутри блока текста). Если вы используете селектор CSS для этого, весь текст в дочерних тегах будет проигнорирован.
Также строка последнего обновления разбирается из нижнего колонтитула страницы и сохраняется в переменной lastUpdated.
Вы можете запустить этот пример, перейдя в каталог wikiSpider/wikiSpider и выполнить следующее:
$ scrapy runspider articles.py
Предупреждение: Этот сканер будет выполняться из командной строки так же, как и предыдущий, но он не завершится (по крайней мере, не очень, очень долго), пока вы не прервёте выполнение, используя Ctrl-C или закрытием терминала. Пожалуйста, будьте добры к нагрузке на сервер Википедии и не запускайте его на долгий срок.
При запуске этот сканер проходит по всему сайту wikipedia.org, следуя всем ссылкам в домене wikipedia.org, выводя заголовки страниц и игнорируя все внешние ссылки (вне сайта):
2018-01-21 01:30:36 [scrapy.spidermiddlewares.offsite] DEBUG: Filtered offsite request to 'www.chicagomag.com': <GET http://www.chicagomag.com/Chicago-Magazine/June-2009/Street-Wise/>
2018-01-21 01:30:36 [scrapy.downloadermiddlewares.robotstxt] DEBUG: Forbidden by robots.txt: <GET https://en.wikipedia.org/w/index.php?title=Adrian_Holovaty&action=edit§ion=3>
title is: Ruby on Rails
URL is: https://en.wikipedia.org/wiki/Ruby_on_Rails
text is: ['Not to be confused with ', 'Ruby (programming language)', '.', '\n', '\n', 'Ruby on Rails', ... ]
Last updated: 9 January 2018, at 10:32.
На данный момент это довольно хороший краулер, но его можно улучшить, добавив несколько ограничений. Вместо того чтобы просто посещать статьи на Википедии, он свободно перемещается по страницам не относящимся к статьям, таким как:
title is: Wikipedia:General disclaimer
Давайте ближе рассмотрим эту строку, используя правило Scrapy и LinkExtractor:
rules = [Rule(LinkExtractor(allow=r'.*'), callback='parse_items', follow=True)]
Эта строка предоставляет список объектов Scrapy Rule, которые определяют правила, через которые проходят все найденные ссылки. Когда применяются несколько правил, каждая ссылка проверяется по правилам последовательно. Первое совпадающее правило используется для определения того, как обрабатывать ссылку. Если ссылка не соответствует ни одному правилу, она игнорируется.
Правилу можно предоставить шесть аргументов:
- link_extractor: Обязательный аргумент, объект LinkExtractor.
- callback: Функция, которая должна использоваться для анализа содержимого страницы.
- cb_kwargs: Словарь аргументов, которые должны быть переданы в функцию обратного вызова. Этот словарь форматируется как {имя_аргумента1: значение_аргумента1, имя_аргумента2: значение_аргумента2} и может быть удобным инструментом для повторного использования одних и тех же функций разбора для несколько различных задач.
- follow: Указывает, нужно ли включить ссылки, найденные на этой странице, в будущий обход. Если функция обратного вызова не предоставлена, это по умолчанию устанавливается в True (ведь если вы ничего не делаете с этой страницей, имеет смысл, что вы хотели бы использовать её для продолжения обхода сайта). Если функция обратного вызова предоставлена, это по умолчанию устанавливается в False.
LinkExtractor — это простой класс, предназначенный исключительно для распознавания и возврата ссылок на странице HTML-контента на основе предоставленных ему правил. У него есть несколько аргументов, которые могут использоваться для принятия или отклонения ссылки на основе селекторов CSS и XPath, тегов (вы можете искать ссылки не только в якорных тегах!), доменов и т. д. Класс LinkExtractor даже может быть расширен, и могут быть созданы пользовательские аргументы. Смотрите документацию Scrapy по извлекателям ссылок для получения дополнительной информации.
Несмотря на все гибкие возможности класса LinkExtractor, наиболее распространенными аргументами, вероятно, будут следующие:
- allow Разрешить все ссылки, которые соответствуют предоставленному регулярному выражению.
- deny Запретить все ссылки, которые соответствуют предоставленному регулярному выражению.
Используя два отдельных класса Rule и LinkExtractor с одной функцией анализа, вы можете создать паука, который обходит Википедию, идентифицируя все статьи и помечая страницы не являющиеся статьями (articlesMoreRules.py):
from scrapy.contrib.linkextractors import LinkExtractor
from scrapy.contrib.spiders import CrawlSpider, Rule
class ArticleSpider(CrawlSpider):
name = 'articles'
allowed_domains = ['wikipedia.org']
start_urls = ['https://en.wikipedia.org/wiki/Benevolent_dictator_for_life']
rules = [
Rule(LinkExtractor(allow='^(/wiki/)((?!:).)*$'), callback='parse_items', follow=True, cb_kwargs={'is_article': True}),
Rule(LinkExtractor(allow='.*'), callback='parse_items', cb_kwargs={'is_article': False})
]
def parse_items(self, response, is_article):
print(response.url)
title = response.css('h1::text').extract_first()
if is_article:
url = response.url
text = response.xpath('//div[@id="mw-content-text"]//text()').extract()
lastUpdated = response.css('li#footer-info-lastmod::text').extract_first()
lastUpdated = lastUpdated.replace('This page was last edited on ', '')
print('Title is: {} '.format(title))
print('title is: {} '.format(title))
print('text is: {}'.format(text))
else:
print('This is not an article: {}'.format(title))
Напомним, что правила применяются к каждой ссылке в том порядке, в котором они представлены в списке. Все страницы статей (страницы, которые начинаются с /wiki/ и не содержат двоеточия) сначала передаются в функцию parse_items с параметром по умолчанию is_article=True. Затем все остальные ссылки, не относящиеся к статьям, передаются в функцию parse_items с аргументом is_article=False.
Конечно, если вы хотите собирать только статьи и игнорировать все остальные страницы, этот подход был бы не практичен. Было бы намного проще игнорировать страницы, не соответствующие шаблону URL статьи, и не включать второе правило (и переменную is_article) вообще. Однако такой подход может быть полезен в случаях, когда информация из URL или информация, собранная во время обхода, влияет на то, как должна быть разобрана страница.
Создание объектов
До этого вы рассмотрели множество способов нахождения, разбора и обхода веб-сайтов с помощью Scrapy, но Scrapy также предоставляет полезные инструменты для организации собранных элементов и их хранения в пользовательских объектах с четко определенными полями.
Чтобы помочь организовать всю информацию, которую вы собираете, вам нужно создать объект Article. Определите новый элемент под названием Article внутри файла items.py.
При открытии файла items.py он должен выглядеть следующим образом:
# -*- coding: utf-8 -*-
# Определите здесь модели для ваших собранных элементов
# Смотрите документацию в:
# http://doc.scrapy.org/en/latest/topics/items.html
import scrapy
class WikispiderItem(scrapy.Item):
# Определите здесь поля для вашего элемента, например:
# name = scrapy.Field()
pass
Замените заглушку Item на новый класс Article, расширяющий scrapy.Item:
import scrapy
class Article(scrapy.Item):
url = scrapy.Field()
title = scrapy.Field()
text = scrapy.Field()
lastUpdated = scrapy.Field()
Вы определяете три поля, которые будут собираться с каждой страницы: заголовок, URL и дата последнего редактирования страницы.
Если вы собираете данные для нескольких типов страниц, вы должны определить каждый отдельный тип как собственный класс в файле items.py. Если ваши элементы большие, или вы начинаете перемещать больше функциональности разбора в ваши объекты элементов, вы также можете захотеть извлечь каждый элемент в свой собственный файл. Однако, пока элементы маленькие, я предпочитаю хранить их в одном файле.
В файле articleSpider.py отметьте изменения, внесенные в класс ArticleSpider, чтобы создать новый элемент Article:
from scrapy.contrib.linkextractors import LinkExtractor
from scrapy.contrib.spiders import CrawlSpider, Rule
from wikiSpider.items import Article
class ArticleSpider(CrawlSpider):
name = 'articleItems'
allowed_domains = ['wikipedia.org']
start_urls = ['https://en.wikipedia.org/wiki/Benevolent_dictator_for_life']
rules = [
Rule(LinkExtractor(allow='(/wiki/)((?!:).)*$'), callback='parse_items', follow=True),
]
def parse_items(self, response):
article = Article()
article['url'] = response.url
article['title'] = response.css('h1::text').extract_first()
article['text'] = response.xpath('//div[@id="mw-content-text"]//text()').extract()
lastUpdated = response.css('li#footer-info-lastmod::text').extract_first()
article['lastUpdated'] = lastUpdated.replace('This page was last edited on ', '')
return article
Когда этот файл запускается с помощью:
$ scrapy runspider articleItems.py
он выведет обычные отладочные данные Scrapy, а также каждый элемент статьи в виде словаря Python:
2018-01-21 22:52:38 [scrapy.spidermiddlewares.offsite] DEBUG: Filtered offsite request to 'wikimediafoundation.org': <GET https://wikimediafoundation.org/wiki/Terms_of_Use>
2018-01-21 22:52:38 [scrapy.core.engine] DEBUG: Crawled (200) <GET https://en.wikipedia.org/wiki/Benevolent_dictator_for_life#mw-head> (referer: https://en.wikipedia.org/wiki/Benevolent_dictator_for_life)
2018-01-21 22:52:38 [scrapy.core.scraper] DEBUG: Scraped from <200 https://en.wikipedia.org/wiki/Benevolent_dictator_for_life>
{'lastUpdated': ' 13 December 2017, at 09:26.', 'text': ['For the political term, see ', 'Benevolent dictatorship', '.', ...]}
Сохранение данных в Scrapy
Scrapy использует объекты Item для определения того, какие куски информации он должен сохранить с посещенных им страниц. Эта информация может быть сохранена Scrapy различными способами, такими как CSV, JSON или XML файлы, с использованием следующих команд:
$ scrapy runspider articleItems.py -o articles.csv -t csv
$ scrapy runspider articleItems.py -o articles.json -t json
$ scrapy runspider articleItems.py -o articles.xml -t xml
Каждая из этих команд запускает скрапер articleItems и записывает вывод в указанном формате в предоставленный файл. Этот файл будет создан, если он еще не существует.
Вы могли заметить, что в пауке articles, созданном в предыдущих примерах, переменная text представляет собой список строк, а не одну строку. Каждая строка в этом списке представляет собой текст внутри одного HTML элемента, в то время как содержимое внутри <div id=»mw-content-text»>, из которого вы собираете текстовые данные, состоит из множества дочерних элементов.
Scrapy хорошо управляет этими более сложными значениями. Например, в формате CSV он преобразует списки в строки и экранирует все запятые, чтобы список текста отображался в одной ячейке CSV.
В XML каждый элемент этого списка сохраняется внутри тегов значений дочерних элементов:
<items>
<item>
<url>https://en.wikipedia.org/wiki/Benevolent_dictator_for_life</url>
<title>Benevolent dictator for life</title>
<text>
<value>For the political term, see </value>
<value>Benevolent dictatorship</value>
...
</text>
<lastUpdated> 13 December 2017, at 09:26.</lastUpdated>
</item>
....
JSON формат сохраняет списки как списки. Конечно, вы можете использовать объекты элементов и записывать их в файл или базу данных так, как вам удобно, просто добавив соответствующий код в функцию разбора веб-сканера.
Пайплайн элементов
Хотя Scrapy работает в одном потоке, он способен создавать и обрабатывать множество запросов асинхронно. Это делает его быстрее, чем скраперы, написанные до этого в этой книге, хотя я всегда был твердым сторонником того, что быстрее не всегда означает лучше, когда речь идет о веб-скрапинге.
Веб-сервер для сайта, который вы пытаетесь спарсить, должен обрабатывать каждый из этих запросов, и важно быть хорошим гражданином и оценить, уместно ли такое нагружение сервера (или даже мудро ли для ваших собственных интересов, так как многие веб-сайты имеют возможность и желание блокировать то, что они могут считать злонамеренной деятельностью по скрапингу). Для получения дополнительной информации об этике веб-скрапинга, а также о важности адекватного ограничения скраперов см. раздел 18.
Сказав это, использование пайплайна элементов Scrapy может еще больше увеличить скорость вашего веб-скрапера, выполняя все обработку данных во время ожидания возвращения запросов, а не ожидая обработки данных перед выполнением следующего запроса. Такая оптимизация иногда даже может быть необходима, когда обработка данных требует много времени или должны выполняться вычисления, требующие мощного процессора.
Чтобы создать пайплайн элементов, перейдите к файлу settings.py, который был создан в начале главы. Вы должны увидеть следующие закомментированные строки:
Раскомментируйте последние три строки и замените их на следующее:
ITEM_PIPELINES = {
'wikiSpider.pipelines.WikispiderPipeline': 300,
}
Это предоставляет класс Python wikiSpider.pipelines.WikispiderPipeline, который будет использоваться для обработки данных, а также целое число, представляющее порядок выполнения пайплайна, если есть несколько классов обработки. Хотя здесь можно использовать любое целое число, обычно используются числа от 0 до 1000, и они будут выполняться в порядке возрастания.
Теперь вам нужно добавить класс пайплайна и переписать ваш первоначальный паук так, чтобы паук собирал данные, а пайплайн выполнял основную работу по обработке данных. Может быть искушение написать метод parse_items в вашем первоначальном пауке так, чтобы он возвращал ответ и позволял пайплайну создавать объект Article:
def parse_items(self, response):
return response
Однако фреймворк Scrapy не позволяет этого, и должен возвращаться объект Item (такой как Article, который расширяет Item). Таким образом, цель метода parse_items сейчас состоит в том, чтобы извлечь сырые данные, сделав как можно меньше обработки, чтобы их можно было передать в пайплайн:
from scrapy.contrib.linkextractors import LinkExtractor
from scrapy.contrib.spiders import CrawlSpider, Rule
from wikiSpider.items import Article
class ArticleSpider(CrawlSpider):
name = 'articlePipelines'
allowed_domains = ['wikipedia.org']
start_urls = ['https://en.wikipedia.org/wiki/Benevolent_dictator_for_life']
rules = [
Rule(LinkExtractor(allow='(/wiki/)((?!:).)*$'),
callback='parse_items', follow=True),
]
def parse_items(self, response):
article = Article()
article['url'] = response.url
article['title'] = response.css('h1::text').extract_first()
article['text'] = response.xpath('//div[@id='
'"mw-content-text"]//text()').extract()
article['lastUpdated'] = response.css('li#'
'footer-info-lastmod::text').extract_first()
return article
Этот файл сохраняется как articlePipelines.py в репозитории GitHub.
Конечно, теперь вам нужно связать файл settings.py и обновленного паука вместе, добавив пайплайн. Когда проект Scrapy был инициализирован в первый раз, был создан файл settings.py в wikiSpider/wikiSpider/settings.py:
class WikispiderPipeline(object):
def process_item(self, item, spider):
return item
Этот заглушечный класс следует заменить вашим новым кодом пайплайна. В предыдущих разделах вы собирали два поля в необработанном формате, и ими можно было бы воспользоваться для дополнительной обработки: lastUpdated (который представляет собой плохо отформатированный строковый объект, представляющий дату) и text (беспорядочный массив строковых фрагментов).
Следующий код следует использовать для замены заглушечного кода в wikiSpider/wikiSpider/settings.py:
from datetime import datetime
from wikiSpider.items import Article
from string import whitespace
class WikispiderPipeline(object):
def process_item(self, article, spider):
dateStr = article['lastUpdated']
article['lastUpdated'] = article['lastUpdated'].replace('This page was last edited on', '')
article['lastUpdated'] = article['lastUpdated'].strip()
article['lastUpdated'] = datetime.strptime(article['lastUpdated'], '%d %B %Y, at %H:%M.')
article['text'] = [line for line in article['text'] if line not in whitespace]
article['text'] = ''.join(article['text'])
return article
Класс WikispiderPipeline содержит метод process_item, который принимает объект Article, парсит строку lastUpdated в объект datetime Python и очищает и объединяет текст в одну строку из списка строк.
process_item является обязательным методом для каждого класса пайплайна. Scrapy использует этот метод для асинхронной передачи элементов, собранных пауком. Обработанный объект Article, возвращаемый здесь, будет зарегистрирован или напечатан Scrapy, если, например, вы выводите элементы в JSON или CSV, как это было сделано в предыдущем разделе.
Теперь у вас есть два варианта, когда дело доходит до решения, где выполнять обработку данных: метод parse_items в пауке или метод process_item в пайплайне.
В файле settings.py можно объявить несколько пайплайнов с разными задачами. Однако Scrapy передает все элементы, независимо от их типа, в каждый пайплайн по порядку. Обработка элементов с учетом их типа может быть лучше реализована в пауке, до того как данные попадут в пайплайн. Однако, если эта обработка занимает много времени, стоит рассмотреть возможность перемещения ее в пайплайн (где она может быть обработана асинхронно) и добавления проверки на тип элемента:
def process_item(self, item, spider):
if isinstance(item, Article):
# Article-specific processing here
Какую обработку выполнять и где ее выполнять — это важное соображение при написании проектов на Scrapy, особенно больших.
Журналирование (логи) в Scrapy
Отладочная информация, создаваемая Scrapy, может быть полезной, но, как вы вероятно заметили, она часто слишком многословна. Вы легко можете настроить уровень журналирования, добавив строку в файл settings.py в вашем проекте Scrapy:
LOG_LEVEL = 'ERROR'
Scrapy использует стандартную иерархию уровней журналирования, следующим образом:
- CRITICAL
- ERROR
- WARNING
- DEBUG
- INFO
Если установлено журналирование на ERROR , будут отображаться только CRITICAL и ERROR журналы. Если журналирование установлено на INFO , будут отображаться все журналы, и так далее.
Помимо управления журналированием через файл settings.py, вы можете контролировать, куда направляются журналы из командной строки. Чтобы выводить журналы в отдельный файл журнала вместо терминала, определите файл журнала при запуске из командной строки:
$ scrapy crawl articles -s LOG_FILE=wiki.log
Это создает новый файл журнала, если он не существует, в вашем текущем каталоге и выводит все журналы в него, оставляя ваш терминал свободным для отображения только инструкций Python print, которые вы добавляете вручную.
Дальнейшее изучение Scrapy
Scrapy представляет собой мощный инструмент, который решает множество проблем, связанных с обходом веб-сайтов. Он автоматически собирает все URL-адреса и сравнивает их с заранее определенными правилами, убеждается, что все URL-адреса уникальны, нормализует относительные URL-адреса там, где это необходимо, и рекурсивно переходит на более глубокие страницы.
Хотя эта глава едва касается возможностей Scrapy, я призываю вас ознакомиться с документацией по Scrapy, а также с книгой «Learning Scrapy» Димитриоса Кузиса-Лукаса (издательство O’Reilly), которая предлагает всестороннее рассмотрение этой библиотеки.
Scrapy — это крайне обширная библиотека с множеством функций. Ее функции взаимодействуют между собой без проблем, но имеют множество областей перекрытия, позволяющих пользователям легко разрабатывать свой собственный стиль работы с ней. Если у вас есть что-то, что вы хотели бы сделать с помощью Scrapy и о чем здесь не упоминается, вероятно, есть способ (или несколько способов) сделать это!
Индивидуальное и групповое обучение «Аналитик данных»
Если вы хотите стать экспертом в аналитике, могу помочь. Запишитесь на мой курс «Аналитик данных» и начните свой путь в мир ИТ уже сегодня!
Контакты
Для получения дополнительной информации и записи на курсы свяжитесь со мной:
Телеграм: https://t.me/Vvkomlev
Email: victor.komlev@mail.ru
Объясняю сложное простыми словами. Даже если вы никогда не работали с ИТ и далеки от программирования, теперь у вас точно все получится! Проверено десятками примеров моих учеников.
Гибкий график обучения. Я предлагаю занятия в мини-группах и индивидуально, что позволяет каждому заниматься в удобном темпе. Вы можете совмещать обучение с работой или учебой.
Практическая направленность. 80%: практики, 20% теории. У меня множество авторских заданий, которые фокусируются на практике. Вы не просто изучаете теорию, а сразу применяете знания в реальных проектах и задачах.
Разнообразие учебных материалов: Теория представлена в виде текстовых уроков с примерами и видео, что делает обучение максимально эффективным и удобным.
Понимаю, что обучение информационным технологиям может быть сложным, особенно для новичков. Моя цель – сделать этот процесс максимально простым и увлекательным. У меня персонализированный подход к каждому ученику. Максимальный фокус внимания на ваши потребности и уровень подготовки.