Как специалист по анализу данных или энтузиаст данных, человек всегда жаждет большого количества ДАННЫХ. Я могу себе представить, как вы видите на веб-сайте много данных и ваше желание собрать все данные, применить все виды техник, которым вы научились, применить статистику, машинное обучение; иногда это может быть для развлечения, для обучения или для каких-то деловых целей, но вы знаете, что сбор большого количества данных - самая трудоемкая часть в жизни специалиста по данным.

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

Я собираюсь показать вам очень простой способ очистить веб-сайт и очень быстро собрать нужные данные на примере очистки с amazon.com. Это можно использовать для любого веб-сайта для любых данных. Мы не будем использовать ASIN, который Amazon использует для предоставления идентификатора своему каталогу, вместо этого мы будем использовать общий подход, который можно использовать для любого веб-сайта.

Мы создадим простой скрипт на Python и немного магии многопоточности / многопроцессорности, чтобы сделать это намного быстрее. (да, в этом случае работает многопоточность, поскольку мы имеем дело в основном с вызовами ввода-вывода и, следовательно, без проблем с GIL). Мы будем использовать очередь для получения всех ответов от различных процессов / потоков, которые мы будем использовать для сохранения данных в фреймворке данных, а затем сохраним их в файле CSV.

Предположим, вам нужно очистить данные с amazon.com, и вам нужно сохранить названия продуктов, цены и рейтинги, и я предполагаю, что вы, возможно, захотите выполнить некоторый анализ данных по этим продуктам; следовательно, сохраните его в кадре данных pandas и CSV для дальнейшего анализа.

Как наблюдать полезные данные в HTML?

  1. Откройте страницу, на которой вы хотите получить данные. Откройте, нажав 2-ю или 3-ю или любую из страниц для поиска. Вы увидите, что URL-адрес выглядит примерно так: https://www.amazon.com/s?k=laptops&page=2&qid=1567174464&ref=sr_pg_2. Вы можете удалить & qid = 1567174464 & ref = sr_pg_2 и нажать Enter, все еще работает. Таким образом, это становится вашим эффективным URL: https://www.amazon.com/s?k=laptops&page=2

2. Щелкните правой кнопкой мыши данные, теги которых вы хотите просмотреть (например, название продукта), а затем нажмите «Проверить». Это откроет HTML для этой страницы. (Обратите внимание на гифку выше)

3. Обратите внимание на тег, на котором присутствует ваш элемент. В данном случае это имя ноутбука. При нажатии мы получаем элемент span, который содержит этот текст:

‹span class =« a-size-medium a-color-base a-text-normal »›

4. Теперь прокрутите вверх, чтобы увидеть, где находится основной тег, содержащий все необходимые данные:

‹div class =« sg-col-4-of-12 sg-col-8-of-16 sg-col-16-of-24 sg-col-12-of-20 sg-col -24-из-32 sg-col sg-col-28-of-36 sg-col-20-of-28 ”›

Обратите внимание, что родительский тег всех необходимых элементов (поскольку, когда мы наводим курсор на этот тег, все необходимые элементы выделяются, как показано в gif)

Теперь нам нужно просто найти элементы ‹span› цены и рейтинга, которые присутствуют в том же теге div. Итак, все необходимые теги:

родительский тег: ‹div class =« sg-col-4-of-12 sg-col-8-of-16 sg-col-16-of-24 sg-col-12-of- 20 sg-col-24-of-32 sg-col sg-col-28-of-36 sg-col-20-of-28 ”›

название продукта: ‹span class =« a-size-medium a-color-base a-text-normal »›

цена: ‹span class =« class = a-offscreen ’» ›

рейтинг: ‹span class =« class = a-icon-alt ’» ›

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

Из библиотек, которые мы собираемся использовать, запросы и beautifulSoup являются наиболее важными библиотеками для очистки Интернета. Запросы используются для выполнения HTTP-запросов через Интернет. BeautifulSoup - это библиотека, которая занимается самой беспорядочной частью: HTML и XML. Здесь мы будем использовать его возможности HTML, поскольку он обеспечивает структуру для объектов HTML, к которым затем можно будет легко получить доступ.

import requests # required for HTTP requests: pip install requests
from bs4 import BeautifulSoup # required for HTML and XML parsing                                                              # required for HTML and XML parsing: pip install beautifulsoup4
import pandas as pd # required for getting the data in dataframes : pip install pandas
import time # to time the requests
from multiprocessing import Process, Queue, Pool
import threading
import sys

Остальные весь импорт не требует пояснений. Теперь нам нужно определить некоторые переменные. Посмотрим на них по очереди:

proxies = { # define the proxies which you want to use
  'http': 'http://195.22.121.13:443',
  'https': 'http://195.22.121.13:443',
}
startTime = time.time()
qcount = 0 # the count in queue used to track the elements in queue
products=[] # List to store name of the product
prices=[] # List to store price of the product
ratings=[] # List to store ratings of the product
no_pages = 9 # no of pages to scrape in the website (provide it via arguments)

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

Теперь действие начинается:

Мы рассмотрим код по частям. Обратите внимание на жирную часть кода:

def get_data(pageNo,q):
    headers = {"User-Agent":"Mozilla/5.0 (Windows NT 10.0; Win64;     x64; rv:66.0) Gecko/20100101 Firefox/66.0", "Accept-Encoding":"gzip, deflate",     "Accept":"text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8", "DNT":"1","Connection":"close", "Upgrade-Insecure-Requests":"1"}
    r = requests.get("https://www.amazon.com/s?k=laptops&page="+str(pageNo), headers=headers)#,proxies=proxies)
    content = r.content
    soup = BeautifulSoup(content)
for d in soup.findAll('div', attrs={'class':'sg-col-4-of-12 sg-col-8-of-16 sg-col-16-of-24 sg-col-12-of-20 sg-col-24-of-32 sg-col sg-col-28-of-36 sg-col-20-of-28'}):
        name = d.find('span', attrs={'class':'a-size-medium a-color-base a-text-normal'})
        price = d.find('span', attrs={'class':'a-offscreen'})
        rating = d.find('span', attrs={'class':'a-icon-alt'})
        all=[]
if name is not None:
            all.append(name.text)
        else:
            all.append("unknown-product")
if price is not None:
            all.append(price.text)
        else:
            all.append('$0')
if rating is not None:
            #print(rating.text)
            all.append(rating.text)
        else:
            all.append('-1')
        q.put(all)
        print("---------------------------------------------------------------")
results = []

Веб-страница, на которую мы пытаемся получить доступ: «https://www.amazon.com/s?k=laptops&page=1

Попробуем разобраться в этом методе def get_data (pageNo, q). Здесь метод принимает в качестве входных данных pageNo и queue. Эта очередь является общей очередью, которая используется между процессами / потоками для обновления после получения результата.

Здесь библиотека запросов используется для HTTP-вызовов на веб-страницу Amazon. Важно отметить, что заголовок необходим, поскольку он заставит серверы Amazon поверить, что это настоящий запрос, а не от ботов (посмотрите, Amazon, мы вас поняли: P).

def get_data(pageNo,q):
headers = {"User-Agent":"Mozilla/5.0 (Windows NT 10.0; Win64;     x64; rv:66.0) Gecko/20100101 Firefox/66.0", "Accept-Encoding":"gzip, deflate",     "Accept":"text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8", "DNT":"1","Connection":"close", "Upgrade-Insecure-Requests":"1"}
r = requests.get("https://www.amazon.com/s?k=laptops&page="+str(pageNo), headers=headers)#,proxies=proxies)
    content = r.content
    soup = BeautifulSoup(content)
for d in soup.findAll('div', attrs={'class':'sg-col-4-of-12 sg-col-8-of-16 sg-col-16-of-24 sg-col-12-of-20 sg-col-24-of-32 sg-col sg-col-28-of-36 sg-col-20-of-28'}):
        name = d.find('span', attrs={'class':'a-size-medium a-color-base a-text-normal'})
        price = d.find('span', attrs={'class':'a-offscreen'})
        rating = d.find('span', attrs={'class':'a-icon-alt'})
        all=[]
if name is not None:
            all.append(name.text)
        else:
            all.append("unknown-product")
if price is not None:
            all.append(price.text)
        else:
            all.append('$0')
if rating is not None:
            #print(rating.text)
            all.append(rating.text)
        else:
            all.append('-1')
        q.put(all)
results = []

Теперь идет красивая часть кода, использующая BeautifulSoup.

В этом процессе всего 4 шага:

content = r.content
  • Создайте объект контента, используя request.content (вы можете использовать request.text, контент позволяет вам получать двоичные данные, а также изображения или PDF-файлы)
soup = BeautifulSoup(content)
  • Создайте объект Beautifulsoup из этого содержимого, что упростит анализ этого объекта содержимого.
for d in soup.findAll('div', attrs={'class':'sg-col-4-of-12 sg-col-8-of-16 sg-col-16-of-24 sg-col-12-of-20 sg-col-24-of-32 sg-col sg-col-28-of-36 sg-col-20-of-28'})
  • Используя цикл for, выполните итерацию по объекту soup, чтобы найти все блоки, в которых class имеет вид «sg-col-4-of-12 sg-col-8-of-16 sg-col-16-of-24 sg-col-12-. из-20 sg-col-24-из-32 sg-col sg-col-28-из-36 sg-col-20-из-28.
  • Извлеките название, продукт, рейтинг и т. Д. Т.е. интересующий вас объект, используя теги span и их относительные имена классов. добавьте его к объекту all, который затем будет вставлен в очередь.

Это создает метод для одного потока / процесса, который будет выполнять запрос, улучшать и извлекать объекты, а затем вставлять их в очередь (совместно используемую всеми процессами / потоками, использующими Manager).

В этом случае вы также можете использовать Manager.list / dict вместо Queue

Основной метод (запуск потоков / процессов):

if __name__ == "__main__":
    m = Manager()
    q = m.Queue()
    p = {}
    if sys.argv[1] in ['t', 'p']:
        for i in range(1,no_pages):
            if sys.argv[1] in ['t']:
                print("starting thread: ",i)
                p[i] = threading.Thread(target=get_data, args=(i,q))
                p[i].start()
            elif sys.argv[1] in ['p']:
                print("starting process: ",i)
                p[i] = Process(target=get_data, args=(i,q))
                p[i].start()
        for i in range(1,no_pages): # join all the threads/processes
            p[i].join()
    else:
        pool_tuple = [(x,q) for x in range(1,no_pages)]
        with Pool(processes=8) as pool:
            print("in pool")
            results = pool.starmap(get_data, pool_tuple)
        print(results)
while q.empty() is not True:
        qcount = qcount+1
        queue_top = q.get()
        products.append(queue_top[0])
        prices.append(queue_top[1])
        ratings.append(queue_top[2])
print("total time taken: ", str(time.time()-startTime), " qcount: ", qcount)
    df = pd.DataFrame({'Product Name':products, 'Price':prices, 'Ratings':ratings})
    df.to_csv('products.csv', index=False, encoding='utf-8')

В основном методе я представляю три способа вызова метода get_data с использованием параллелизма и параллелизма:

  • используя потоки
  • используя процессы
  • используя бассейн.
if sys.argv[1] in ['t', 'p']:

Вышеупомянутый условный оператор позволяет нам запускать поток или процесс в случае, если пользователь вводит аргумент как t / p.

for i in range(1,no_pages):

Этот цикл for выполняется для указанных no_pages. Я жестко запрограммировал это. Вы можете передать его как аргумент от пользователя. Это будет добавлено к URL-адресу, и каждый URL-адрес будет добавлен в новый поток / процесс:

например. Https://www.amazon.com/s?k=laptops&page=1 работает в одном потоке, затем« https://www.amazon.com/s?k=laptops&page= nu2 в другом и так далее. ..

p[i] = threading.Thread(target=get_data, args=(i,q))
p[i].start() or
p[i] = Process(target=get_data, args=(i,q))
p[i].start()

Объект Manager.Queue передается потоку / процессу, поскольку он помогает создать общую очередь для всех процессов / потока. Вышеупомянутая часть создаст поток / процесс в зависимости от ввода пользователя, а затем запустит поток / процесс.

for i in range(1,no_pages): 
    p[i].join()

Теперь, когда потоки / процессы запущены, нам нужно объединить их в отдельном цикле, чтобы остальная часть программы ожидала выполнения этих потоков, иначе остальная часть программы будет продолжена, а потоки будут продолжать работать в фоновом режиме. .

pool_tuple = [(x,q) for x in range(1,no_pages)]
        with Pool(processes=8) as pool:
            print("in pool")
            results = pool.starmap(get_data, pool_tuple)

Если пользователь не указывает ни один из входных параметров в виде t или p, это означает, что он по умолчанию запускает метод пула для распараллеливания. Мы генерируем список кортежей пула, который имеет формат: (номер_страницы, очередь): [(1, q), (2, q)…] и т. Д.

В: зачем здесь использовать метод звездной карты?

О: Поскольку здесь мы передаем более одного аргумента, нам нужно использовать метод starmap.

Теперь получите плоды своего труда:

Обработка завершена, в очереди все данные от всех процессов. Теперь нам нужно получить данные из очереди, что очень просто:

while q.empty() is not True:
        qcount = qcount+1
        queue_top = q.get()
        products.append(queue_top[0])
        prices.append(queue_top[1])
        ratings.append(queue_top[2])

Мы перебираем очередь до тех пор, пока она не станет пустой, используя queue.get, где каждый полученный элемент находится в форме списка, который затем может быть извлечен и добавлен к спискам продуктов, цен и рейтингов.

df = pd.DataFrame({'Product Name':products, 'Price':prices, 'Ratings':ratings})
df.to_csv('products.csv', index=False, encoding='utf-8')

В этой последней части кода мы создаем фрейм данных из списков и сохраняем его как CSV с помощью pandas.

И в результате ...

Здесь у вас есть красиво созданный файл products.csv с названием продукта, ценой и рейтингом. Таким образом, вы успешно очистили amazon.com.

Важно помнить:

Не используйте объект очереди многопроцессорной обработки, так как это вызовет ошибку в случае объектов пула. Всегда используйте Manager. Объект очереди, использованный выше. Честно говоря, везде, где я исследовал, это единственное решение, но почему многопроцессорность. Очередь не работает, я все еще не могу найти ответ. Я поднял вопрос о StackOverflow, но тщетно.

Усовершенствования, которые следует учитывать:

  1. Вместо того, чтобы жестко указывать «портативные компьютеры» как ярлыки и количество страниц, вы можете передать его как вводимый пользователем список, а затем перебрать все ярлыки, если вам нужны данные для нескольких продуктов.
  2. Попробуйте использовать другие теги на веб-сайте и почистите другую информацию о продукте.
  3. В случае разных ярлыков, таких как ноутбуки, телевизоры, мобильные телефоны, поскольку мы используем многопоточность, данные будут случайными при извлечении из очереди, потому что любой поток / процесс мог быть вставлен в очередь в любое время. На помощь приходит фрейм данных; так как очень быстро и легко преобразовать этот фрейм данных в отдельные фреймы данных (и, следовательно, CSV) на основе имени метки.

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

  • Используйте разные прокси и ротацию прокси с прокси-сервера в библиотеке запросов. Для ротации прокси многие бесплатные прокси можно найти с помощью «freeproxy.com», «hidemyass.com», а таких веб-сайтов тысячи. Некоторые прокси-сайты предоставляют аутентичные прокси, если вы не хотите слишком долго искать прокси по очень номинальной цене.
  • Это - очень хорошая статья, которая мне показалась очень полезной, в ней описаны методы, позволяющие сэкономить ваши задницы во время соскабливания (каламбур: D).

Ссылка GitHub на код находится здесь:



Пожалуйста, прокомментируйте любые сомнения / обсуждения. Я вернусь с еще несколькими статьями, связанными с наукой о данных, питоном и пандами. Для дальнейших обсуждений свяжитесь со мной в LinkedIn. А пока, желаю удачи.