Ваш секретный чек-лист по парсингу

Добрый день! В этой статье я поделюсь своим опытом парсинга большого количества сайтов.

Задание:

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

Что необходимо сделать перед началом парсинга:

  • Убедиться в корректности ссылок сайтов: Корректно указанный протокол (исключаем протоколы: hhtp , hhtps , hhtps , hpp и прочие вариации )Отсутствие пробелов, точек и прочих спец символов в начале и конце ссылки (прим. ./http://www.____.ru/. )
  • Проверить работоспособность сайтовМножество сайтов в датасете были недоступны по тем или иным причинам (прим. нерабочий сайт, ошибка 404, 403 и т.д.)

Для реализации обработки выбран Python, использованы библиотеки:

# Парсинг import requests from bs4 import BeautifulSoup as BS # Обработка текста import re # Работа с файлами import json import os #Очистка вывода from IPython.display import clear_output

Если вы уверены в корректности своих ссылок, этот пункт можно пропустить.

Для первичной обработки пунктов 1 и 2 использовалась функция check_url() , которая принимает в себя ссылку , а возвращает её же исправленную, и код ответа с сайта :

# Список найденных неправильных протоколов bad_list = ['https','http', 'hhtp://' ,'hhtps://' ,'hhtps://', 'hpp:', 'hpps:', 'hppt', 'hppts', 'htt', 'htth', 'htto', 'htpps', '^', 'httpp', 'httt','ttps'] def cheek_url(url): s = url # Удаление протоколов из ссылки for i in bad_list: s = s.replace(i,'') # Удаление первого символа из спец символов в списке while s[0] in ['/','.',';',':',',']: s = s[1:] # Удаление последнего символа из спец символов в списке while s[-1] in ['/','.',';',':',',']: s = s[:-1] url = s s_c = 'bad_url' try: url = f'https://{s}' # Попытка обратиться к сайту с обновленным защищенным протоколом response = requests.get(url,timeout = 60) s_c = response.status_code if s_c == 200: return url, s_c elif s_c != 'bad_url': return url, s_c except: try: # Попытка обратиться к сайту с обновленным незащищенным протоколом url = f'http://{s}' response = requests.get(url,timeout = 60) s_c = response.status_code if s_c == 200: return url, s_c elif s_c != 'bad_url': return url, s_c except: return url, s_c

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

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

Долговременное хранение потребует проработки архитектуры хранения сайтов, для последующей работы с ними. Также стоит учитывать вес загруженных текстов и, соответственно, место для хранения. Некоторых сайты с принятыми ограничениями указанными ниже весили до 7ГБ, 5000 сайтов весят в среднем ~ 50 ГБ в не заархивированном состоянии ( в архиве они же ~ 7 ГБ).

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

имя файла — id сайта в изначальной таблице

ключи внутри — ссылки страниц

file = { "ссылка на сайт" : { "status" : "статус сайта (200,404 и т.д.)", "text" : "текст, загруженный с сайта ", "img" : [список ссылок на картинки с сайта] }, "ссылка на сайт" : {...}, ... }

И последнее при подготовке — это определиться, как вы будете хранить данные о кодах сайтов, данные обработки и т.д.Я использовал для этого таблицы .csv и .xlsx, а работу с ними в python производил с помощью библиотеки pandas.

import pandas as pd df = pd.read_csv('sites.csv', index_col=0) df
Ваш секретный чек-лист по парсингу

Парсинг

Закончив с первичной обработкой, приступаем к парсингу рабочих сайтов.

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

def get_all_links(page): try: HEADERS = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:87.0) Gecko/20100101 Firefox/87.0'} html_text = requests.get(page, headers=HEADERS, timeout = 60).text soup = BS(html_text) arr = [] end = ['.jpg','.pdf','.exe','.mp4','.jpeg'] for link in soup.find_all('a', href=True): #Пропуск ссылок, оканчивающиеся на расширения файлов ch = False for e in end: if link.endswith(e): ch = True if ch: continue l = link['href'] #добавление имени главной страницы сайта, если таковая отсутствует if l.find("//") == -1 : try: if l[0] != "/": l = page + '/' + l else: l = page + l except: pass if l!='': arr.append(l) return [el for el, _ in groupby(arr)] except: return[]

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

%%time i = 0 start_time = time() directory = 'sites' # Создание папки под файлы, если еще не создана if not os.path.isdir(directory): os.mkdir(directory) HEADERS = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:87.0) Gecko/20100101 Firefox/87.0'} l1 = len(df) for row in df.itertuples(index=True, name='Pandas'): print( f"Url_n {row.Index} of {l1}. URL = {row.URL}" ) data= {} # Проверка, не создали ли мы уже файл с таким индексом if os.path.isfile(f'{directory}/{row.Index}.json'): i+=1 continue j = 0 # Получаем ссылки со страницы, и ставим главную страницу на первое место в списке list_url = get_all_links(row.URL) list_url.insert(0,row.URL) error = 0 imgUrls = [] for url in list_url: clear_output(True) print(f"i = {i} / {l1}. URL = {row.URL} . name = {row.Index }") print(f"page {j} of {len(list_url)}. URL = {url}. Error = {error} . Time = {time() - start_time}") j+=1 list_img = [] try: try: html = requests.get(url,headers=HEADERS,timeout = 60).text except: data[url]= {'status':'cheek one more',"text":'','img': imgUrls} continue soup = BS(html) text = soup.body.get_text() # Получаем список с ссылками на все картинки на странице imgUrls = re.findall('img .*?src="(.*?)"', html) data[url] = {'status':'ok',"text":text,'img': imgUrls} except: error +=1 data[url] = {'status':'error load page',"text":'','img': imgUrls} # Сохраняем json файл с загруженным сайтом with open( f'{directory}/{row.Index}.json','w',encoding="utf-8") as file: json.dump(data,file) i+=1

Данный процесс можно ускорить, запустив несколько вкладок в Jupyter Notebook, обрабатывая разные части дата фрейма.

Заключение.

По итогу мы получаем файлы с текстом, готовые к обработке.

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

При обработке тяжелых сайтов ( ~4гб) может закончится озу, и загруженные данные обрезаются.

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

P.S.Описанным здесь методом не получится загрузить сайты, защищенные от парсинга.

22
1 комментарий

Комментарий недоступен