Pull to refresh

Мониторинг позиций своими руками

Search engines *Python *
Tutorial

Делаем мониторинг позиций запросов в поисковой системе, начало.


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

Наш инструмент будет состоять из 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 или управления ценами
Tags:
Hubs:
Total votes 28: ↑20 and ↓8 +12
Views 20K
Comments 55
Comments Comments 55