Остановка скрипта без ошибок
У меня есть скрипт, который рекурсивно собирает ссылки с сайта с некоторыми ограничениями.
В определенный момент скрипт завершает свое выполнение на этой функции. Причем в абсолютно разное время и на разных ссылках
def get_tree_free(link):
"""Получаем html дерево"""
logging.info(f"Ссылка - {link}")
logging.info("Начало выполнения get_tree_free")
r = requests.get(link, headers={'User-Agent': UserAgent().chrome})
logging.info(f"статус код - {r.status_code}")
if r.status_code == 200:
logging.info("Возвращаем нормальное дерево")
return BeautifulSoup(r.content, 'html.parser')
else:
logging.info("Статус код НЕ 200")
...
Пытался отловить хотя бы какие-то исключения используя try-except и библиотеку loguru, но никаких ошибок нет. Просто программа прекращает действие.
Оперативная память не забивается, все ссылки вставляются в БД и берутся тоже оттуда. Также проверял этот пункт с помощью memory_profiler, он не показывает увеличение использованной оперативки.
Прочитал все возможные вопросы на форумах, описывающие похожую проблему, но ничего так и не помогло.
Единственное предположение - в requests есть какой-то ограничитель или баг, который не дает дальше выполнять скрипт, но, повторю, что никаких ошибок нет.
Конец лог-файла, в который я записывал информацию, используя logging:
INFO:root:ТУТ НАЧАЛОСЬ ВЫПОЛНЕНИЕ ПАРСИНГА НОВОЙ СТРАНИЦЫ
INFO:root:Выполняется функция get_tree_urls
INFO:root:Выполняется функция check_key_words и append_urls
INFO:root:Запуск get_next_url
INFO:root:Зашло в get_next_url
INFO:root:1603809398.2826648, время
INFO:root:Выполнение условия
INFO:root:Получание нового элемента из стека
INFO:root:Получение tree
INFO:root:Начало выполнения get_tree
INFO:root:Ссылка - https://www.tesli.com/catalog/eui/rozetki-i-vyklyuchateli/rozetki/?display_type=list&sort=article&%09%09%09PAGEN_1=2&PAGEN_1=3
INFO:root:Начало выполнения get_tree_free
INFO:root:статус код - 200
INFO:root:Возвращаем нормальное дерево
INFO:root:ТУТ НАЧАЛОСЬ ВЫПОЛНЕНИЕ ПАРСИНГА НОВОЙ СТРАНИЦЫ
INFO:root:Выполняется функция get_tree_urls
INFO:root:Выполняется функция check_key_words и append_urls
INFO:root:Запуск get_next_url
INFO:root:Зашло в get_next_url
INFO:root:1603809403.8433385, время
INFO:root:Выполнение условия
INFO:root:Получание нового элемента из стека
INFO:root:Получение tree
INFO:root:Начало выполнения get_tree
INFO:root:Ссылка - https://www.tesli.com/catalog/eui/rozetki-i-vyklyuchateli/rozetki/?sort=article&display_type=tile&%09%09%09PAGEN_1=2& PAGEN_1=4
INFO:root:Начало выполнения get_tree_free
INFO:root:статус код - 200
INFO:root:Возвращаем нормальное дерево
INFO:root:ТУТ НАЧАЛОСЬ ВЫПОЛНЕНИЕ ПАРСИНГА НОВОЙ СТРАНИЦЫ
INFO:root:Выполняется функция get_tree_urls
INFO:root:Выполняется функция check_key_words и append_urls
INFO:root:Запуск get_next_url
INFO:root:Зашло в get_next_url
INFO:root:1603809409.6905885, время
INFO:root:Выполнение условия
INFO:root:Получание нового элемента из стека
INFO:root:Получение tree
INFO:root:Начало выполнения get_tree
INFO:root:Ссылка - https://www.tesli.com/catalog/eui/rozetki-i-vyklyuchateli/rozetki/?display_type=tile&sort=article&%09%09%09PAGEN_1=2&PAGEN_1=4
INFO:root:Начало выполнения get_tree_free
Если у кого-то была такая проблема, пожалуйста, отзовитесь!
Обновление
Исходный код:
class CatCollect:
"""
Класс описывает все методы, необходимые для сбора всех
ссылок, которые находятся глубже заданного URL
"""
@profile()
def __init__(self, url):
logging.basicConfig(filename='script_log.log', level=logging.INFO)
logging.info(f"{time.time()} start run")
self.sql = SQLTest()
self.sql.get_connection()
logger.add("debug.log", format="{time} {level} {message}", level="DEBUG", rotation="10 MB", compression="zip")
self.link = main_link # link
self.scheme_domain, self.url = urlparse(self.link).scheme + '://' + urlparse(self.link).hostname, \
urlparse(self.link).path
self.waste_words = ['js', 'ico', 'https', 'css', 'png'] # значения, которых не должно быть в ссылке
self.proxies = []
self.tree = BeautifulSoup(requests.get(self.link).content, 'html.parser')
self.get_tree_urls()
def get_tree(self, link):
"""Объединение методов получение html дерева"""
logging.info('Начало выполнения get_tree')
if self.proxies:
return self.get_tree_proxy(link)
else:
return self.get_tree_free(link)
@staticmethod
def get_tree_free(link):
"""Получаем html дерево"""
logging.info(f"Ссылка - {link}")
logging.info("Начало выполнения get_tree_free")
r = requests.get(link, headers={'User-Agent': UserAgent().chrome})
logging.info(f"статус код - {r.status_code}")
if r.status_code == 200:
logging.info("Возвращаем нормальное дерево")
return BeautifulSoup(r.content, 'html.parser')
else:
logging.info("Статус код НЕ 200, дальше return")
return None
def get_tree_proxy(self, link):
"""Получаем html дерево, используя прокси"""
logging.info("Какого то пошло в proxy")
pass
@logger.catch()
def get_tree_urls(self, tree=False):
"""Получаем все ссылки со страницы"""
# Временный список для url со страницы
logging.info(f"Выполняется функция get_tree_urls")
tree = tree if tree is not False else self.tree
links = tree.find_all(href=True)
logging.info(f"Выполняется функция check_waste_words и append_urls")
for link in links:
url = link.get('href')
self.check_waste_words(url)
logging.info('Запуск get_next_url')
self.get_next_url()
def check_waste_words(self, url):
"""Проверка на наличие в ссылке ненужные значения"""
time_dict = {} # Временный словарь, чтобы не сохранить несколько
for word in self.waste_words:
if word not in url:
time_dict[url] = 1
else:
try:
del time_dict[url]
except KeyError:
break
self.append_urls(time_dict)
def append_urls(self, time_dict):
"""Добавляет url'ы в стеки"""
for url in time_dict.keys():
exist = self.sql.select_exist(url, 'links', 'link') # Проверка существования данного url в БД
if not exist:
self.sql.insert_link(url, 'links')
self.select_valid_url(url)
def select_valid_url(self, url):
"""Отбирает ссылки, по которым можно продолжать парсинг"""
if self.url in url:
# Добавляет ссылку, по которой можно продолжить собирать данные
self.sql.insert_link(url, 'cor_links')
else:
return None
def concat_domain_url(self, url):
"""Собираем рабочую ссылку из домена и url"""
return self.scheme_domain + url
def get_next_url(self):
"""Получает из БД следующую ссылку на сбора url'ов"""
logging.info('Зашло в get_next_url')
logging.info(f"{time.time()}, время")
try:
new_url = self.sql.select_cor_link() # Получение новой ссылки
except TypeError:
return None # Условие остановки программы
self.sql.delete_cor_link(new_url) # Удаление полученной новой ссылки
full_link = self.concat_domain_url(new_url)
try:
logging.info('Получение tree')
tree = self.get_tree(full_link)
except (urllib3.exceptions.NewConnectionError, requests.exceptions.ConnectionError) as e:
logging.info(f'Ошибка получения дерева, {e}')
self.get_next_url()
try:
logging.info('ТУТ НАЧАЛОСЬ ВЫПОЛНЕНИЕ ПАРСИНГА НОВОЙ СТРАНИЦЫ')
self.get_tree_urls(tree)
except Exception as e:
logging.info(f'Да я ошибку{e}')
self.get_next_url()
logging.info(f"Почему то тут закончилось действие")
Ответы (1 шт):
Решение найдено! Из-за хоть и ограниченной, но очень долгой рекурсии переполнялся стек и программа завершалась. Переписал скрипт, добавив 2 новые функции, в одной из которых есть цикл while:
class CatCollect:
"""
Класс описывает все методы, необходимые для сбора всех
ссылок, которые находятся глубже заданного URL
"""
@profile
def __init__(self, url):
self.sql = SQLTest()
self.sql.get_connection()
self.link = link_here # url
self.scheme_domain, self.url = urlparse(self.link).scheme + '://' + urlparse(self.link).hostname, \
urlparse(self.link).path
self.proxies = []
self.first_parse()
def first_parse(self):
"""Получение первых страниц"""
tree = self.get_tree(self.link)
self.get_tree_urls(tree)
self.pars()
return None
def pars(self):
"""Главная функция, которая объединяет все остальные функции"""
while True:
new_url = self.get_next_url()
if new_url:
try:
tree = self.get_tree(new_url)
except (requests.exceptions.ConnectionError, urllib3.exceptions.NewConnectionError):
continue
self.get_tree_urls(tree)
else:
return None
h = hp.heap()
with open('memory_strict_control.txt', 'a') as f:
print(h, file=f)
print('\n', file=f)
def get_tree(self, link):
"""Объединение методов получение html дерева"""
if self.proxies:
return self.get_tree_proxy(link)
else:
return self.get_tree_free(link)
@staticmethod
def get_tree_free(link):
"""Получаем html дерево"""
try:
r = requests.get(link, headers={'User-Agent': UserAgent().chrome}, timeout=2)
except requests.exceptions.Timeout:
return None
if r.status_code == 200:
return BeautifulSoup(r.content, 'html.parser')
else:
return None
def get_tree_proxy(self, link):
"""Получаем html дерево, используя прокси"""
pass
def get_tree_urls(self, tree):
"""Получаем все ссылки со страницы"""
# Временный список для url со страницы
links = tree.find_all(href=True)
self.iterate_links(links)
return None
def iterate_links(self, links):
"""Перебираем ссылки и добавляем их"""
for link in links:
url = link.get('href')
self.append_urls(url)
return None
def append_urls(self, url):
"""Добавляет url'ы в стеки"""
exist = self.sql.select_exist(url, 'links', 'link') # Проверка существования данного url в БД
if not exist:
self.sql.insert_link(url, 'links')
self.select_valid_url(url)
return None
def select_valid_url(self, url):
"""Отбирает ссылки, по которым можно продолжать парсинг"""
if self.url in url:
self.sql.insert_link(url, 'cor_links') # Добавляет ссылку, по которой можно продолжить собирать данные
else:
return None
def concat_domain_url(self, url):
"""Собираем рабочую ссылку из домена и url"""
return self.scheme_domain + url
def get_next_url(self):
"""Получает из БД следующую ссылку на сбора url'ов"""
try:
new_url = self.sql.select_cor_link() # Получение новой ссылки
self.sql.delete_cor_link(new_url) # Удаление полученной новой ссылки
full_link = self.concat_domain_url(new_url)
return full_link
except TypeError:
return None # Условие остановки программы