Делаем мониторинг позиций запросов в поисковой системе, начало.
Обычно мы заинтересованны в увеличении клиентов.
А что-бы увеличить что-то, нужно это сначало оценить.
А так уж исторически сложилось, что часть клиентов на интернет-магазины приходит с поисковых систем.
( Про работу с контекстной рекламой и прайс-агрегаторами напишу в следующих статьях, если кому будет интересно. )
А для оценики своего состояния в поисковиках, обычно нужно собрать с них статистику по положению запросов в выдаче.
Наш инструмент будет состоять из 2-х частей:
- скрипт для парсинга поисковой выдачи, с помощью Curl и lxml
- веб-интерфейс для управления и отображения, на Django
Узнаем у yandex.ru нашу позицию по запросу.
Хочу сразу уточнить, в данной статье будет описаны самые азы и сделаем самый простой вариант, который в дальнейшем будем усовершенствовать.
Для начала сделаем функцию которая по урлу возвращает html.
Загружать страницу будем c помощью pycurl.
import pycurl c = pycurl.Curl()
Установим url который будем загружать
url = 'ya.ru' c.setopt(pycurl.URL, url)
Для возврата тела страницы curl использует callback функцию, которой передает строку с html.
Воспользуемся строковым буфером StringIO, на вход у него есть функция write(), а забирать все содержимое из него мы сможем через getvalue()
from StringIO import StringIO c.bodyio = StringIO() c.setopt(pycurl.WRITEFUNCTION, c.bodyio.write) c.get_body = c.bodyio.getvalue
На всякий случай сделаем наш curl похожим на броузер, пропишем таймауты, юзерагента, заголовки и т.д.
c.setopt(pycurl.FOLLOWLOCATION, 1) c.setopt(pycurl.MAXREDIRS, 5) c.setopt(pycurl.CONNECTTIMEOUT, 60) c.setopt(pycurl.TIMEOUT, 120) c.setopt(pycurl.NOSIGNAL, 1) c.setopt(pycurl.USERAGENT, 'Mozilla/5.0 (X11; Ubuntu; Linux i686; rv:13.0) Gecko/20100101 Firefox/13.0') httpheader = [ 'Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8', 'Accept-Language: ru-ru,ru;q=0.8,en-us;q=0.5,en;q=0.3', 'Accept-Charset:utf-8;q=0.7,*;q=0.5', 'Connection: keep-alive', ] c.setopt(pycurl.HTTPHEADER, httpheader)
Теперь загружаем страницу
c.perform()
Вот и все, страница у нас, мы можем прочитать html страницы
print c.get_body()
Так-же можем прочитать заголовки
print c.getinfo(pycurl.HTTP_CODE)
И если получили получили какой-то отличный от 200 ответ сервера, то сможем его обработать. Сейчас мы просто выкинем исключение, обрабатывать исключения будем в следующих статьях
if c.getinfo(pycurl.HTTP_CODE) != 200: raise Exception('HTTP code is %s' % c.getinfo(pycurl.HTTP_CODE))
Обернем все что получилось в функцию, в итоге у нас получилось
import pycurl try: from cStringIO import StringIO except ImportError: from StringIO import StringIO def get_page(url, *args, **kargs): c = pycurl.Curl() c.setopt(pycurl.URL, url) c.bodyio = StringIO() c.setopt(pycurl.WRITEFUNCTION, c.bodyio.write) c.get_body = c.bodyio.getvalue c.headio = StringIO() c.setopt(pycurl.HEADERFUNCTION, c.headio.write) c.get_head = c.headio.getvalue c.setopt(pycurl.FOLLOWLOCATION, 1) c.setopt(pycurl.MAXREDIRS, 5) c.setopt(pycurl.CONNECTTIMEOUT, 60) c.setopt(pycurl.TIMEOUT, 120) c.setopt(pycurl.NOSIGNAL, 1) c.setopt(pycurl.USERAGENT, 'Mozilla/5.0 (X11; Ubuntu; Linux i686; rv:13.0) Gecko/20100101 Firefox/13.0') httpheader = [ 'Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8', 'Accept-Language: ru-ru,ru;q=0.8,en-us;q=0.5,en;q=0.3', 'Accept-Charset:utf-8;q=0.7,*;q=0.5', 'Connection: keep-alive', ] c.setopt(pycurl.HTTPHEADER, httpheader) c.perform() if c.getinfo(pycurl.HTTP_CODE) != 200: raise Exception('HTTP code is %s' % c.getinfo(pycurl.HTTP_CODE)) return c.get_body()
Проверим функцию
print get_page('ya.ru')
Выберем из страницы поисковой выдачи список сайтов с позициями
Сконструируем поисковый запрос,
на yandex.ru/yandsearch нам нужно послать 3 GET параметра,
'text'-запрос, 'lr'-регион поиска, 'p'-страница выдачи
import urllib import urlparse key='кирпич' region=213 page=1 params = ['http', 'yandex.ru', '/yandsearch', '', '', ''] params[4] = urllib.urlencode({ 'text':key, 'lr':region, 'p':page-1, }) url = urlparse.urlunparse(params)
Выведем url и проверим в броузере
print url
Получим через предыдущую функцию страницу с выдачей
html = get_page(url)
Теперь будем ее разбирать по dom модели с помощью lxml
import lxml.html site_list = [] for h2 in lxml.html.fromstring(html).find_class('b-serp-item__title'): b = h2.find_class('b-serp-item__number') if len(b): num = b[0].text.strip() url = h2.find_class('b-serp-item__title-link')[0].attrib['href'] site = urlparse.urlparse(url).hostname site_list.append((num, site, url))
Поподробнее напишу что тут происходит
lxml.html.fromstring(html) — из html строки мы делаем обьект html документа
.find_class('b-serp-item__title') — ищем по документу все теги, которые содержат класс 'b-serp-item__title', получаем список элементов H2 которые содержат интерсующую нас информацию по позициям, и проходим по ним циклом
b = h2.find_class('b-serp-item__number') — ищем внутри найденого тега H2 элемент b, кторый содержит номер позиции сайта, если нашли то дальше собираем позицию b[0].text.strip() сайта и строчку c url сайта
urlparse.urlparse(url).hostname — получаем доменное имя
Проверим получившийся список
print site_list
И соберем все получившееся в функцию
def site_list(key, region=213, page=1): params = ['http', 'yandex.ru', '/yandsearch', '', '', ''] params[4] = urllib.urlencode({ 'text':key, 'lr':region, 'p':page-1, }) url = urlparse.urlunparse(params) html = get_page(url) site_list = [] for h2 in lxml.html.fromstring(html).find_class('b-serp-item__title'): b = h2.find_class('b-serp-item__number') if len(b): num = b[0].text.strip() url = h2.find_class('b-serp-item__title-link')[0].attrib['href'] site = urlparse.urlparse(url).hostname site_list.append((num, site, url)) return site_list
Проверим функцию
print site_list('кирпич', 213, 2)
Найдем наш сайт в списке сайтов
Нам потребуется вспомогательная функция, которая отрезает 'www.' в начале сайта
def cut_www(site): if site.startswith('www.'): site = site[4:] return site
Получим список сайтов и сравним с нашим сайтом
site = 'habrahabr.ru' for pos, s, url in site_list('python', 213, 1): if cut_www(s) == site: print pos, url
Хм, на первой стрнице выдачи по 'python' хабра нету, попробуем пройти выдачу в цикле в глубину,
но нам нужно поставить ограничение, max_position — до какой позиции мы будем проверять,
заодно и обернем в функцию, а на случай, если ничего не найдется будем возвращать None, None
def site_position(site, key, region=213, max_position=10): for page in range(1,int(math.ceil(max_position/10.0))+1): site = cut_www(site) for pos, s, url in site_list(key, region, page): if cut_www(s) == site: return pos, url return None, None
Проверяем
print site_position('habrahabr.ru', 'python', 213, 100)
Вот собственно мы и получили нашу позицию.
Напишите пожалста, интересна ли данная тема и нужно-ли продолжать?
Про что написать в следующей статье?
— сделать вебинтерфейс к этой функции с табличками и графиками и повесить скрипт на cron
— сделать обработку капчи для этой функции, и вручную и через специальные api
— сделать скрипт мониторинга чего-нить с многопоточностью
— описать работу с директом на примере генерации и заливки обьявлений через api или управления ценами
