CoderCastrov logo
CoderCastrov
Парсер

Парсинг веб-страниц для извлечения контактной информации — Рассылки

Парсинг веб-страниц для извлечения контактной информации — Рассылки
просмотров
7 мин чтение
#Парсер

Когда вы начинаете работать с наукой о данных и машинным обучением, вы замечаете, что есть важная вещь, которую вы чаще всего будете упускать, пытаясь решить проблему: данные!

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

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

Существует много бесплатных пакетов Python, сосредоточенных на парсинге веб-страниц, краулинге и анализе, таких как Requests, Selenium и Beautiful Soup, поэтому, если хотите, посмотрите некоторые из них и решите, какой из них лучше всего подходит вам. Эта статья дает краткое введение в некоторые из основных библиотек.

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

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

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

Эта проблема будет решена в 5 шагов:

1 — Извлечение веб-сайтов из Google с помощью googlesearch 2 — Создание регулярного выражения для извлечения электронной почты 3 — Парсинг веб-сайтов с использованием Scrapy 4 — Сохранение этих электронных адресов в CSV-файле 5 — Сборка всего вместе

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


1 — Извлечение веб-сайтов из Google с помощью googlesearch

Для извлечения URL-адресов из тега мы воспользуемся библиотекой googlesearch. У этой библиотеки есть метод search, который по заданному запросу, количеству веб-сайтов и языку вернет ссылки из поиска Google. Но перед вызовом этой функции давайте импортируем несколько модулей:

import logging
import os
import pandas as pd
import re
import scrapy
from scrapy.crawler import CrawlerProcess
from scrapy.linkextractors.lxmlhtml import LxmlLinkExtractor
from googlesearch import search
logging.getLogger('scrapy').propagate = False

Последняя строка используется для того, чтобы избежать получения слишком много журналов и предупреждений при использовании Scrapy внутри Jupyter Notebook.

Итак, давайте создадим простую функцию с search:

def get_urls(tag, n, language):
    urls = [url for url in search(tag, stop=n, lang=language)][:n]
    return urls

Этот код возвращает список строк URL-адресов. Давайте проверим:

get_urls('рейтинг фильма', 5 , 'ru')

Отлично. Теперь этот список URL-адресов (назовем его google_urls) будет использоваться в качестве входных данных для нашего Spider, который будет считывать исходный код каждой страницы и искать электронные адреса.

2 — Создание регулярного выражения для извлечения электронных адресов

Если вас интересует обработка текста, я настоятельно рекомендую ознакомиться с регулярными выражениями (regex), потому что после их освоения становится довольно легко манипулировать текстом, искать шаблоны в строках - находить, извлекать и заменять части текста - на основе последовательности символов. Например, извлечение электронных адресов можно выполнить с помощью метода findall, следующим образом:

mail_list = re.findall(‘\w+@\w+\.{1}\w+’, html_text)

Используемое здесь выражение ‘\w+@\w+.1\w+’ можно перевести примерно так:

"Ищите каждый фрагмент строки, который начинается с одной или более букв, за которыми следует символ at (‘@’), за которым идут одна или более буквы с точкой в конце. После этого должны быть снова одна или более буквы".

Если вам интересно узнать больше о regex, на YouTube есть отличные видео, включая это введение от Sentdex, и документация может помочь вам начать.

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

3 — Парсинг веб-сайтов с помощью парсера Scrapy

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

class MailSpider(scrapy.Spider):
    
    name = 'email'
    
    def parse(self, response):
        
        links = LxmlLinkExtractor(allow=()).extract_links(response)
        links = [str(link.url) for link in links]
        links.append(str(response.url))
        
        for link in links:
            yield scrapy.Request(url=link, callback=self.parse_link) 
            
    def parse_link(self, response):
        
        for word in self.reject:
            if word in str(response.url):
                return
            
        html_text = str(response.text)        mail_list = re.findall('\w+@\w+\.{1}\w+', html_text)

        dic = {'email': mail_list, 'link': str(response.url)}
        df = pd.DataFrame(dic)
        
        df.to_csv(self.path, mode='a', header=False)
        df.to_csv(self.path, mode='a', header=False)

Разберем его по частям. Парсер принимает список URL-адресов в качестве входных данных и последовательно считывает их исходные коды. Вы, возможно, заметили, что мы ищем не только электронные адреса в URL-адресах, но и ссылки. Это потому, что на большинстве веб-сайтов контактная информация не будет находиться прямо на главной странице, а скорее на странице контактов или подобной. Поэтому в первом методе parse мы запускаем объект извлекателя ссылок (LxmlLinkExtractor), который проверяет новые URL-адреса внутри исходного кода. Эти URL-адреса передаются методу parse_link - это фактический метод, в котором мы применяем наш regex findall для поиска электронных адресов.

Фрагмент кода ниже отвечает за отправку ссылок из одного метода парсинга в другой. Это достигается с помощью аргумента callback, который определяет, к какому методу должен быть отправлен URL-адрес запроса.

yield scrapy.Request(url=link, callback=self.parse_link)

Внутри parse_link мы можем заметить цикл for, использующий переменную reject. Это список слов, которые нужно избегать при поиске веб-адресов. Например, если я ищу тег 'рестораны в Рио-де-Жанейро', но не хочу наткнуться на страницы facebook или twitter, я могу включить эти слова в список нежелательных слов при создании парсера:

process = CrawlerProcess({'USER_AGENT': 'Mozilla/5.0'})
process.crawl(MailSpider, start_urls=google_urls, path=path, reject=reject)
process.start()

Список google_urls будет передан в качестве аргумента при вызове метода process для запуска парсера, path определяет, куда сохранить файл CSV, а reject работает, как описано выше.

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

4 — Сохраните эти электронные письма в CSV-файле

Scrapy также имеет свои собственные методы для сохранения и экспорта извлеченных данных, но в этом случае я использую свой собственный (возможно, более медленный) способ с использованием метода to_csv из библиотеки pandas. Для каждого просканированного веб-сайта я создаю таблицу данных с колонками: [email, link], и добавляю ее в ранее созданный CSV-файл.

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

def ask_user(question):
    response = input(question + ' y/n' + '\n')
    if response == 'y':
        return True
    else:
        return False

def create_file(path):
    response = False
    if os.path.exists(path):
        response = ask_user('Файл уже существует, заменить?')
        if response == False: return 
    
    with open(path, 'wb') as file: 
        file.close()

5 — Соберем все вместе

Наконец, мы создадим основную функцию, в которой все будет работать вместе. Сначала она создает пустой фрейм данных и записывает его в новый CSV-файл, затем получает google_urls с помощью функции get_urls и запускает процесс выполнения нашего парсера.

def get_info(tag, n, language, path, reject=[]):
    
    create_file(path)
    df = pd.DataFrame(columns=['email', 'link'], index=[0])
    df.to_csv(path, mode='w', header=True)
    
    print('Сбор Google URL-адресов...')
    google_urls = get_urls(tag, n, language)
    
    print('Поиск электронных адресов...')
    process = CrawlerProcess({'USER_AGENT': 'Mozilla/5.0'})
    process.crawl(MailSpider, start_urls=google_urls, path=path, reject=reject)
    process.start()
    
    print('Очистка электронных адресов...')
    df = pd.read_csv(path, index_col=0)
    df.columns = ['email', 'link']
    df = df.drop_duplicates(subset='email')
    df = df.reset_index(drop=True)
    df.to_csv(path, mode='w', header=True)
    
    return df

Хорошо, давайте протестируем его и проверим результаты:

bad_words = ['facebook', 'instagram', 'youtube', 'twitter', 'wiki']
df = get_info('студия звукозаписи Лондон', 300, 'ru', 'studios.csv', reject=bad_words)

Поскольку я разрешил заменить старый файл, был создан файл studios.csv с результатами. Помимо сохранения данных в CSV-файле, наша конечная функция возвращает фрейм данных с полученной информацией.

df.head()

Вот и все! Несмотря на то, что у нас есть список с тысячами электронных адресов, вы увидите, что среди них некоторые кажутся странными, некоторые даже не являются электронными адресами, и, вероятно, большинство из них окажутся бесполезными для нас. Так что следующим шагом будет поиск способов отфильтровать большую часть нерелевантных электронных адресов и поиск стратегий для сохранения только тех, которые могут быть полезными. Может быть, с помощью машинного обучения? Я надеюсь.


Это все для этого поста. Эта статья была написана в качестве первой части серии, в которой мы используем Scrapy для создания Извлекателя контактов. Спасибо, если вы дочитали до конца. Сейчас я рад написать свой второй пост. Не стесняйтесь оставлять комментарии, идеи или вопросы. И если вам понравилось, не забудьте послать аплодисменты! Увидимся в следующем посте.