Skip to content

Instantly share code, notes, and snippets.

@snakers4
Created June 27, 2017 16:59
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save snakers4/095bb9a43d3165b15b249ce9d77e9006 to your computer and use it in GitHub Desktop.
Save snakers4/095bb9a43d3165b15b249ce9d77e9006 to your computer and use it in GitHub Desktop.
<p></p><p><img src="https://pics.spark-in.me/upload/7b09411ab7b91bc8da7e79c763311795.jpg" style="width: 700px;"></p><p><span style="font-style: italic;">Как всегда надо начать с заманчивой картинки, но не пояснять ее смысл!</span></p><hr><p></p><p style="text-align: justify; ">Пару дней назад ко мне обратился далекий знакомый, мол у нас политесы в компании и мы не можем никак выбрать направление развития. Ситуация типичная - <span style="font-weight: bold;">лебедь, рак и щука</span> и надо бы очень быстро <span style="font-weight: bold;">проанализировать сайты его конкурентов</span>, вот держи картинку со списком самых крупных компаний в этой сфере. Оказалось, что конкуренты из сферы форекса. Это меня смутило, но не остановило. Я посмотрел на их сайты (а, забегая вперед, некоторые из них просто огромны - миллионы страниц) и тут у меня родилась гениальная идея.</p><p style="text-align: justify; ">Я занимался ей пару суток почти без остановки и накопал относительно интересные результаты, которыми и хочу поделиться с вами.</p><hr><p style="text-align: justify; "><img src="https://pics.spark-in.me/upload/ee4923168f58286b1cb17594ef7bc34a.png" style="width: 1127px;"><br></p><p style="text-align: justify; "><span style="font-style: italic;">Картинка, которую мне прислали. Даже nutshell не нужен.</span></p><hr><p style="text-align: justify; "><span style="font-weight: bold;">После визита на пару сайтов, у меня в голове промелькнули такие мысли:</span></p><ol><li style="text-align: justify;">Парсинг (или "<span style="font-weight: bold;">скрепинг"</span>) сайтов в современном мире - это лучшее средство business intelligence. Ибо сейчас все выкладывают весь свой контент для индексации в интернет;</li><li style="text-align: justify;">Недавно я видел отличную и простую&nbsp;<a href="http://pbpython.com/web-scraping-mn-budget.html" target="_blank" style="font-weight: bold; text-decoration-line: underline;">статью</a>, про то, что мол парсинг это чуть ли ваша обязанность, если вы аналитик;</li><li style="text-align: justify;">У меня есть знакомый, которому один раз предлагали спарсить весь вконтакте за 300,000 рублей &nbsp;1 раз. Так вот он говорил, что в одноразовом парсинге на питоне вообще нет ничего сложного. Потоковый парсинг сложнее, там надо иметь прокси, VPN и балансировщик нагрузки и очередь;</li><li style="text-align: justify;">Самые крупные сайты как правило имеют сайтмапы;</li><li style="text-align: justify;">По этой причине этот подход в принципе применим для любой отрасли, где присутствует много контента;</li><li style="text-align: justify;">Идея также расширяется до выборочного парсинга самих страниц, но это на порядок сложнее технически (оставим это более прошаренным коллегам);</li></ol><p style="text-align: justify; "><br></p><p style="text-align: justify; ">Эта статья будет скорее оформлена в виде пошагового гайда, что немного в новинку для меня. Но почему нет, давайте попробуем?</p><h1 style="text-align: justify; "><span style="font-weight: bold;">1. Идея</span></h1><p style="text-align: justify; ">Через секунду после того, как я подумал про сайтмапы, у меня сразу родился план действий:</p><ul><li style="text-align: justify;">Найти все сайтмапы сайтов;</li><li style="text-align: justify;">Внести в массив, указав рекурсивные ли они;</li><li style="text-align: justify;">Собрать итоговый список сайтмапов (а их явно будут сотни или тысячи);</li><li style="text-align: justify;">Спарсить их;</li><li style="text-align: justify;">Распарсить урлы на составляющие;</li><li style="text-align: justify;">Сделать семантический анализ составляющий урлов;</li><li style="text-align: justify;">Если хватит сил, прогнать bag-of-words анализ, снизить размерность через метод главных компонент (PCA) и посмотреть что будет;</li><li style="text-align: justify;">Построить визуализации для самых популярных слов;</li><li style="text-align: justify;">Сделать простейшие сводные таблицы;</li><li style="text-align: justify;">Если будет нужно и полезно - подключить к процессу еще и словесные вектора (word2vec);</li></ul><p style="text-align: justify;"><br></p><p style="text-align: justify;">Забегая вперед, что получилось посмотреть можно тут:</p><ul><li style="text-align: justify;"><a href="https://goo.gl/doBG53" target="_blank" style="font-weight: bold; text-decoration-line: underline;">ipynb</a>;</li><li style="text-align: justify;"><a href="https://resources.spark-in.me/sitemap-analysis.html" target="_blank" style="font-weight: bold; text-decoration-line: underline;">HTML</a>;</li></ul><p style="text-align: justify;">Проще всего вам будет следить за повествованием установив себе зависимости, запустив jupyter notebook и выполняя код последовательно. Внимание (!) - иногда из-за размерности файлов отжирается вся память (на моей &nbsp;песочнице 16ГБ) - будьте внимательны! Для простейшего мониторинга рекомендую <a href="https://nicolargo.github.io/glances/" target="_blank" style="font-weight: bold; text-decoration-line: underline;">glances</a>.<span style="font-weight: bold; font-size: 25px; letter-spacing: -0.015em;"><br></span></p><p style="text-align: justify;"><span style="font-weight: bold; font-size: 25px; letter-spacing: -0.015em;">2. Зависимости (Common libraries, Code progress utility)</span></p><p style="text-align: justify;">Все делается на третьем питоне. </p><ul><li style="text-align: justify;">По сути вам нужен сервер с python3 и <a href="http://jupyter.org" target="_blank" style="font-weight: bold; text-decoration-line: underline;">jupyter notebook</a> (без этого будет гораздо дольше). </li><li style="text-align: justify;">Также я очень советую поставить себе <a href="https://t.me/snakers4/809" target="_blank" style="font-weight: bold; text-decoration-line: underline;">плагин</a>&nbsp;<span style="font-weight: bold;">сollapsable / expandable jupyter cells;</span></li><li style="text-align: justify;">Список основных библиотек и зависимостей указан ниже (или я буду добавлять их в отдельных ячейках);</li></ul><p style="text-align: justify;"><br></p><pre style="text-align: justify;">from __future__ import print_function<br>import os.path<br>from collections import defaultdict<br>import string<br>import requests<br>import pandas as pd<br>import numpy as np<br>import matplotlib.pyplot as plt<br>import random<br>from sklearn.feature_extraction.text import CountVectorizer<br>import wordcloud<br>%matplotlib inline</pre><p style="text-align: justify; "><br></p><p style="text-align: justify; ">Если у вас все работает, то .ipynb файл откроется примерно так (сверните ячейки, если они не свернуты по умолчанию):</p><img src="https://pics.spark-in.me/upload/e794e45912f5da326a1e880b3277e836.png" style="width: 1153px;"><p style="text-align: justify; "><br></p><p style="text-align: justify; ">Также <a href="https://github.com/alexanderkuk/log-progress" target="_blank" style="font-weight: bold; text-decoration-line: underline;">отсюда</a>&nbsp;я взял небольшую утилитку для демонстрации прогресса парсинга. &nbsp;Инструкции по установке также по ссылке.</p><p style="text-align: justify; "><img src="https://pics.spark-in.me/upload/68747470733a2f2f686162726173746f726167652.gif" style="width: 446px;"><br></p><p style="text-align: justify; "><span style="font-size: 25px; font-weight: bold; letter-spacing: -0.375px;">3. Список сайтмапов (Sitemap list)</span><br></p><p style="text-align: justify; ">Тут все банально, гуляем по сайтам, ищем сайтмапы (обычно они лежат в корне с названием sitemap.xml). Поиск google по сайту также помогает. Записываем в лист словарей.</p><pre style="text-align: justify; ">sitemap_list = [&nbsp;<br>&nbsp; &nbsp; {'url': 'https://www.ig.com/sitemap.xml', 'recursive': 1},<br>&nbsp; &nbsp; {'url': 'https://www.home.saxo/sitemap.xml', 'recursive': 0}, &nbsp; &nbsp;<br>&nbsp; &nbsp; {'url': 'https://www.fxcm.com/sitemap.xml', 'recursive': 1}, &nbsp;<br>&nbsp; &nbsp; {'url': 'https://www.icmarkets.com/sitemap_index.xml', 'recursive': 1}, &nbsp;<br>&nbsp; &nbsp; {'url': 'https://www.cmcmarkets.com/en/sitemap.xml', 'recursive': 0},<br>&nbsp; &nbsp; {'url': 'https://www.oanda.com/sitemap.xml', 'recursive': 0}, &nbsp; &nbsp;&nbsp;<br>&nbsp; &nbsp; {'url': 'http://www.fxpro.co.uk/en_sitemap.xml', 'recursive': 0},&nbsp;<br>&nbsp; &nbsp; {'url': 'https://en.swissquote.com/sitemap.xml', 'recursive': 0},&nbsp;<br>&nbsp; &nbsp; {'url': 'https://admiralmarkets.com/sitemap.xml', 'recursive': 0}, &nbsp; &nbsp;&nbsp;<br>&nbsp; &nbsp; {'url': 'https://www.xtb.com/sitemap.xml', 'recursive': 1}, &nbsp; &nbsp; &nbsp;&nbsp;<br>&nbsp; &nbsp; {'url': 'https://www.ufx.com/en-GB/sitemap.xml', 'recursive': 0}, &nbsp;&nbsp;<br>&nbsp; &nbsp; {'url': 'https://www.markets.com/sitemap.xml', 'recursive': 0}, &nbsp;&nbsp;<br>&nbsp; &nbsp; {'url': 'https://www.fxclub.org/sitemap.xml', 'recursive': 1}, &nbsp; &nbsp; &nbsp;&nbsp;<br>&nbsp; &nbsp; {'url': 'https://www.teletrade.eu/sitemap.xml', 'recursive': 1}, &nbsp; &nbsp; &nbsp;&nbsp;<br>&nbsp; &nbsp; {'url': 'https://bmfn.com/sitemap.xml', 'recursive': 0}, &nbsp; &nbsp; &nbsp;&nbsp;<br>&nbsp; &nbsp; {'url': 'https://www.thinkmarkets.com/en/sitemap.xml', 'recursive': 0}, &nbsp;<br>&nbsp; &nbsp; {'url': 'https://www.etoro.com/sitemap.xml', 'recursive': 1}, &nbsp;<br>&nbsp; &nbsp; {'url': 'https://www.activtrades.com/en/sitemap_index.xml', 'recursive': 1}, &nbsp;<br>&nbsp; &nbsp; {'url': 'http://www.fxprimus.com/sitemap.xml', 'recursive': 0}<br>]</pre><p style="text-align: justify; "><br></p><p style="text-align: justify; ">Тут немаловажно, что часть сайтмапов рекурсивная (то есть содержит ссылки на другие сайтмапы), а часть нет.</p><p style="text-align: justify; "><span style="font-size: 25px; font-weight: bold; letter-spacing: -0.375px;">4. Собственно сам сбор сайтмапов (Web scraping)</span></p><p style="text-align: justify; ">Поскольку часть админов указанных выше сайтов проверяют кто забирает сайтмап, можно попробовать на всякий случай притвориться юзером (можно гугл-ботом, но юзером полезнее научиться притворяться =) ). Все сайтмапы по ссылке выше открывались у меня в браузере.&nbsp;</p><p style="text-align: justify; ">Для этого нам поможет библиотека&nbsp;<span style="background-color: rgb(252, 252, 252); font-family: Menlo, Monaco, Consolas, &quot;Courier New&quot;, monospace; font-size: 12px; font-weight: bold;">fake_useragent:</span></p><pre style="text-align: justify; ">from fake_useragent import UserAgent<br>ua = UserAgent()<br>headers = ua.chrome<br>headers = {'User-Agent': headers}</pre><p style="text-align: justify; "><br></p><p style="text-align: justify; ">Если мы попробуем забрать один сайтмап, то мы увидим, что его надо декодировать</p><pre style="text-align: justify; ">result = requests.get(sitemap_list[3]['url'])<br>c = result.content<br>c = c.decode("utf-8-sig")<br>c</pre><p style="text-align: justify; "></p><p style="text-align: justify; ">Ответ выглядит примерно так:</p><blockquote style="text-align: justify; ">'&lt;?xml version="1.0" encoding="UTF-8"?&gt;&lt;?xml-stylesheet type="text/xsl" href="//www.icmarkets.com/main-sitemap.xsl"?&gt;\n&lt;sitemapindex xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"&gt;\n\t&lt;sitemap&gt;\n\t\t&lt;loc&gt;https://www.icmarkets.com/post-sitemap.xml&lt;/loc&gt;\n\t\t&lt;lastmod&gt;2016-12-16T07:13:32-01:00&lt;/lastmod&gt;\n\t&lt;/sitemap&gt;\n\t&lt;sitemap&gt;\n\t\t&lt;loc&gt;https://www.icmarkets.com/page-sitemap.xml&lt;/loc&gt;\n\t\t&lt;lastmod&gt;2017-06-20T07:11:01+00:00&lt;/lastmod&gt;\n\t&lt;/sitemap&gt;\n\t&lt;sitemap&gt;\n\t\t&lt;loc&gt;https://www.icmarkets.com/attachment-sitemap1.xml&lt;/loc&gt;\n\t\t&lt;lastmod&gt;2014-07-01T15:44:46+00:00&lt;/lastmod&gt;\n\t&lt;/sitemap&gt;\n\t&lt;sitemap&gt;\n\t\t&lt;loc&gt;https://www.icmarkets.com/attachment-sitemap2.xml&lt;/loc&gt;\n\t\t&lt;lastmod&gt;2014-10-29T02:36:07-01:00&lt;/lastmod&gt;\n\t&lt;/sitemap&gt;\n\t&lt;sitemap&gt;\n\t\t&lt;loc&gt;https://www.icmarkets.com/attachment-sitemap3.xml&lt;/loc&gt;\n\t\t&lt;lastmod&gt;2015-03-15T18:41:51-01:00&lt;/lastmod&gt;\n\t&lt;/sitemap&gt;\n\t&lt;sitemap&gt;\n\t\t&lt;loc&gt;https://www.icmarkets.com/attachment-sitemap4.xml&lt;/loc&gt;\n\t\t&lt;lastmod&gt;2017-05-30T12:33:34+00:00&lt;/lastmod&gt;\n\t&lt;/sitemap&gt;\n\t&lt;sitemap&gt;\n\t\t&lt;loc&gt;https://www.icmarkets.com/category-sitemap.xml&lt;/loc&gt;\n\t\t&lt;lastmod&gt;2016-12-16T07:13:32-01:00&lt;/lastmod&gt;\n\t&lt;/sitemap&gt;\n\t&lt;sitemap&gt;\n\t\t&lt;loc&gt;https://www.icmarkets.com/post_tag-sitemap.xml&lt;/loc&gt;\n\t\t&lt;lastmod&gt;2014-03-27T01:14:54-01:00&lt;/lastmod&gt;\n\t&lt;/sitemap&gt;\n\t&lt;sitemap&gt;\n\t\t&lt;loc&gt;https://www.icmarkets.com/csscategory-sitemap.xml&lt;/loc&gt;\n\t\t&lt;lastmod&gt;2013-06-11T00:02:10+00:00&lt;/lastmod&gt;\n\t&lt;/sitemap&gt;\n\t&lt;sitemap&gt;\n\t\t&lt;loc&gt;https://www.icmarkets.com/author-sitemap.xml&lt;/loc&gt;\n\t\t&lt;lastmod&gt;2017-05-05T06:44:19+00:00&lt;/lastmod&gt;\n\t&lt;/sitemap&gt;\n&lt;/sitemapindex&gt;\n&lt;!-- XML Sitemap generated by Yoast SEO --&gt;'</blockquote><p style="text-align: justify; "><br></p><p style="text-align: justify; ">Эта функция найденная на просторах интернета поможет нам декодировать XML дерево, которым является сайтмап:<br></p><pre style="text-align: justify; "># xml tree parsing<br>import xml.etree.ElementTree as ET<br><br>def xml2df(xml_data):<br>&nbsp; &nbsp; root = ET.XML(xml_data) # element tree<br>&nbsp; &nbsp; all_records = []<br>&nbsp; &nbsp; for i, child in enumerate(root):<br>&nbsp; &nbsp; &nbsp; &nbsp; record = {}<br>&nbsp; &nbsp; &nbsp; &nbsp; for subchild in child:<br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; record[subchild.tag] = subchild.text<br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; all_records.append(record)<br>&nbsp; &nbsp; return pd.DataFrame(all_records)</pre><p style="text-align: justify; "><br></p><p style="text-align: justify; ">Далее эта функция поможет нам собрать все сайтмапы в одном листе:</p><pre style="text-align: justify; ">end_sitemap_list = []<br>for sitemap in log_progress(sitemap_list, every=1):<br>&nbsp; &nbsp; if(sitemap['recursive']==1):<br>&nbsp; &nbsp; &nbsp; &nbsp; try:<br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; result = requests.get(sitemap['url'], headers=headers)<br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; c = result.content<br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; c = c.decode("utf-8-sig")<br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; df = xml2df(c)<br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; end_sitemap_list.extend(list(df['{http://www.sitemaps.org/schemas/sitemap/0.9}loc'].values))<br>&nbsp; &nbsp; &nbsp; &nbsp; except:<br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; print(sitemap)<br>&nbsp; &nbsp; else:<br>&nbsp; &nbsp; &nbsp; &nbsp; end_sitemap_list.extend([sitemap['url']])</pre><p style="text-align: justify; "><br></p><p style="text-align: justify; ">В разное время у меня получалось от 200 до 250 сайтмапов.</p><p style="text-align: justify; ">В итоге эта функция поможет нам собственно собрать данные сайтмапов и сохранить их в датафрейм pandas.</p><p style="text-align: justify; "><br></p><pre style="text-align: justify; ">result_df = pd.DataFrame(columns=['changefreq','loc','priority'])<br>for sitemap in log_progress(end_sitemap_list, every=1):<br>&nbsp; &nbsp;&nbsp;<br>&nbsp; &nbsp; result = requests.get(sitemap, headers=headers)<br>&nbsp; &nbsp; c = result.content<br>&nbsp; &nbsp; try:<br>&nbsp; &nbsp; &nbsp; &nbsp; c = c.decode("utf-8-sig")<br>&nbsp; &nbsp; &nbsp; &nbsp; df = xml2df(c)<br>&nbsp; &nbsp; &nbsp; &nbsp; columns = [<br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; '{http://www.sitemaps.org/schemas/sitemap/0.9}changefreq',<br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; '{http://www.sitemaps.org/schemas/sitemap/0.9}loc',<br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; '{http://www.sitemaps.org/schemas/sitemap/0.9}priority'<br>&nbsp; &nbsp; &nbsp; &nbsp; ]<br>&nbsp; &nbsp; &nbsp; &nbsp; try:&nbsp;<br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; df2 = df[columns]<br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; df2['source'] = sitemap<br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; df2.columns = ['changefreq','loc','priority','source']<br>&nbsp; &nbsp; &nbsp; &nbsp; except:<br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; df2['loc'] = df['{http://www.sitemaps.org/schemas/sitemap/0.9}loc']<br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; df2['changefreq'] = ''<br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; df2['priority'] = ''<br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; df2['source'] = sitemap<br>&nbsp; &nbsp; &nbsp; &nbsp; result_df = result_df.append(df2)<br>&nbsp; &nbsp; except:<br>&nbsp; &nbsp; &nbsp; &nbsp; print(sitemap)</pre><p style="text-align: justify; "><br></p><p style="text-align: justify; ">После нескольких минут ожидания у нас получается таблица размером&nbsp;(14047393, 4), что весьма неплохо для такого "наколеночного" решения!&nbsp;</p><p style="text-align: justify; ">Если вам понравился новый формат, пишите в личке, будем продолжать в таком же формате. Ну и эта статья - первая в цикле.</p><p style="text-align: justify; "><br></p><p style="text-align: justify; "><br></p><p style="text-align: justify; "><br></p>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment