#python #python-3.x #web-scraping #scrapy
#python #python-3.x #очистка веб-страниц #scrapy
Вопрос:
Я создал spider для анализа ссылки из другого контейнера с целевой страницы некоторого идентичного сайта (предоставленной текстовым файлом), а затем использую ссылку для получения заголовка с ее внутренней страницы. Несколько ссылок имеют кнопку «Следующая страница», которую spider обрабатывает соответствующим образом.
Spider анализирует содержимое, но попадает в бесконечный цикл, вызванный dont_filter=True
параметром. Если я не использую этот параметр, spider не будет повторно использовать некоторые ссылки, которые изначально не привели к желаемому результату.
Я использовал этот параметр dont_filter=True
в трех местах.
- В
_retry()
методе промежуточных программ - В последней строке
parse()
метода - В последней строке
parse_content()
метода
созданный мной spider:
import os
import scrapy
import urllib
from bs4 import BeautifulSoup
from scrapy.crawler import CrawlerProcess
class YelpSpider(scrapy.Spider):
name = "yelpspidescript"
with open("all_urls.txt") as f:
start_urls = f.readlines()
def start_requests(self):
for url in self.start_urls:
yield scrapy.Request(url,callback=self.parse,meta={"lead_link":url})
def parse(self,response):
if response.meta.get("lead_link"):
lead_link = response.meta.get("lead_link")
elif response.meta.get("redirect_urls"):
lead_link = response.meta.get("redirect_urls")[0]
soup = BeautifulSoup(response.text, 'lxml')
if soup.select("[class*='hoverable'] h4 a[href^='/biz/'][name]"):
for item in soup.select("[class*='hoverable'] h4 a[href^='/biz/'][name]"):
lead_link = response.urljoin(item.get("href"))
yield scrapy.Request(lead_link,meta={"lead_link":lead_link},callback=self.parse_content)
next_page = soup.select_one("a[class*='next-link'][href^='/search?']")
if next_page:
link = response.urljoin(next_page.get("href"))
yield scrapy.Request(link,meta={"lead_link":link},callback=self.parse)
else:
yield scrapy.Request(lead_link,meta={"lead_link":lead_link},callback=self.parse,dont_filter=True)
def parse_content(self,response):
if response.meta.get("lead_link"):
lead_link = response.meta.get("lead_link")
elif response.meta.get("redirect_urls"):
lead_link = response.meta.get("redirect_urls")[0]
soup = BeautifulSoup(response.text, 'lxml')
if soup.select_one("h1[class*='heading--inline__']"):
try:
name = soup.select_one("h1[class*='heading--inline__']").get_text(strip=True)
except AttributeError: name = ""
print(name)
else:
yield scrapy.Request(lead_link,meta={"lead_link":lead_link},callback=self.parse_content,dont_filter=True)
if __name__ == "__main__":
c = CrawlerProcess({
'USER_AGENT':'Mozilla/5.0',
'LOG_LEVEL':'ERROR',
})
c.crawl(YelpSpider)
c.start()
промежуточные программы:
from fake_useragent import UserAgent
RETRY_HTTP_CODES = [500, 502, 503, 504, 408, 403, 401, 400, 404, 408]
class yelp_custom_Middleware(object):
ua = UserAgent()
def process_request(self, request, spider):
request.headers['User-Agent'] = self.ua.random
def process_exception(self, request, exception, spider):
return self._retry(request, exception, spider)
def _retry(self, request, reason, spider):
retryreq = request.copy()
retryreq.dont_filter = True
return retryreq
def process_response(self, request, response, spider):
if request.meta.get('dont_retry', False):
return response
if response.status in RETRY_HTTP_CODES:
reason = response_status_message(response.status)
return self._retry(request, reason, spider) or response
return response
Как я могу позволить spider не попадать в бесконечный цикл?
Редактировать: я думал, включать несколько URL-адресов, я изо которые находятся в all_urls.txt
файле в случае, если это помогает определить проблему лучше.
Комментарии:
1. Как насчет добавления счетчика повторных попыток?
2. Если я соглашусь с вашим предложением, как будет выглядеть модифицированная версия
_retry( )
метода @gangabass? Спасибо.3. @robots.txt можете ли вы лучше объяснить, как происходит бесконечный цикл? Я спрашиваю, потому что, хотя фильтр «выключен», он не должен входить в цикл. Единственные случаи, когда я видел, как это происходит, — это когда во время синтаксического анализа страницы spider находит и выдает запрос на ту же страницу Или когда сервер держит вас в бесконечном цикле перенаправления. Другой вопрос: проблема не связана с запросами, выдаваемыми
start_requests
(из списка URL), или это также происходит с запросами, выдаваемыми при разборе страниц?4. Ответ на ваш второй вопрос: происходит с любым случайным URL-адресом, независимо от того, входят ли они в число начальных URL-адресов или они заполнены разными методами @renatodvc. На ваш первый вопрос: я не заметил никакого перенаправления, но я заметил URL-адрес captcha, который скрипт обходит с помощью прокси. Однако суть в том, что я не хочу, чтобы скрипт попадал в бесконечный цикл. Спасибо.
5. Это становится значительно сложнее решить из-за незнания причины возникновения цикла, поскольку это само по себе является аномалией. Считаете ли вы систему, которая ограничивает количество запросов, выполняемых по одному URL, разумным решением?
Ответ №1:
Вы можете подсчитывать повторные попытки для каждого URL:
class yelp_custom_Middleware(object):
ua = UserAgent()
max_retries = 3
retry_urls = {}
def process_request(self, request, spider):
request.headers['User-Agent'] = self.ua.random
def process_exception(self, request, exception, spider):
return self._retry(request, exception, spider)
def _retry(self, request, reason, spider):
retry_url = request.url
if retry_url not in self.retry_urls:
self.retry_urls[retry_url] = 1
else:
self.retry_urls[retry_url] = 1
if self.retry_urls[retry_url] > self.max_retries:
# Dont' retry
else:
# Retry
Комментарии:
1. Как только я запускаю скрипт, я получаю эту ошибку
retry_url = request.retry AttributeError: 'Request' object has no attribute 'retry'
.2. Извините, у меня глупая опечатка:
retry_url = request.url
Ответ №2:
ОТКАЗ ОТ ОТВЕТСТВЕННОСТИ: Это решение является обходным путем к описанной проблеме, когда вы сталкиваетесь с проблемой с бесконечными циклами в запросе, всегда желательно определить причину, а не изменять, как работает фильтр.
-
Создавайте
customdupefilter.py
на том же уровне, что и выmiddlewares.py
илиsettings.py
.import os from scrapy.dupefilters import RFPDupeFilter class CustomDupeFilter(RFPDupeFilter): default_max_retries = 5 def __init__(self, path=None, debug=False): super().__init__(path, debug) self.request_counter = {} def request_seen(self, request): max_retries = request.meta.get('max_retries') or self.default_max_retries fp = self.request_fingerprint(request) if fp in self.fingerprints: if self.request_counter.get(fp) >= max_retries: self.logger.info('Requests to %s exceeded maximum retries.', request) return True else: self.request_counter[fp] = 1 return None self.fingerprints.add(fp) self.request_counter.update({fp: 1}) if self.file: self.file.write(fp os.linesep)
Предполагается, что вы используете стандартную структуру папок проекта scrapy, если вы измените структуру папок или имя файла, просто не забудьте также изменить ссылку в настройках.
-
Добавьте следующую строку в свой
settings.py
DUPEFILTER_CLASS = 'YOUR_PROJECT_NAME.customdupefilter.CustomDupeFilter'
-
Если вы используете
dont_filter=True
в запросе , он обойдет этот фильтр точно так же, как стандартный класс. -
Вы можете отправить в meta запроса
max_retries
параметр, чтобы изменить максимальное количество повторных попыток, разрешенных для этого конкретного запроса, если вы этого не сделаете, он будет использоватьсяdefault_max_retries
. На всякий случай, если для конкретного запроса требуется больше запросов, чем для большинства других. Пример:yield Request(url=request_url, meta={'max_retries': 3})
Объяснение:
Это просто перезапишет стандартный класс filter RFPDupeFilter
и реализует счетчик, чтобы увидеть, сколько раз разрешено повторять один и тот же запрос, как только будет передано максимальное число, он будет фильтроваться точно так же, как и раньше.
Как уже упоминалось, dont_filter
этот метод фильтрации будет обходиться.
Комментарии:
1. Когда вы предлагаете создать
customdupefilter.py
на том же уровнеmiddlewares.py
илиsettings.py
, разве это не то, что вы имели в виду? Спасибо.2. Да, для стандартной структуры проекта scrapy. Вы можете поместить его куда-нибудь еще, если хотите, просто не забудьте обновить
DUPEFILTER_CLASS
вsettings.py
, чтобы scrapy мог его импортировать.3. Поскольку scrapy по умолчанию фильтрует повторяющиеся запросы, когда
dont_filter=True
его нет, будет ли он выполнять повторяющиеся вызовы, если я использую,meta={'max_retry_times':5}
даже когдаdont_filter=True
@renatodvc отсутствует? Спасибо.4. При использовании
dont_filter=True
запрос полностью пропускает фильтр, если не использовать его, запрос пройдет через фильтр. Фильтр будет блокировать запросы, сделанные только после того, как он превысит количествоmax_retries
того же запроса. Обратите внимание, что мета-параметр НЕ являетсяmax_retry_times
. И да, если вы продублируете запрос, scrapy выполнит их оба ( если подmax_retries
), в конце концов, в этом и был смысл. Фильтр не вызовет никаких дубликатов, он просто позволит им произойти (до предела), если вы выдадите дублированные запросы.