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