Какова наилучшая практика для написания поддерживаемых веб-скребок?

Мне нужно реализовать несколько скребок для обхода некоторых веб-страниц (потому что у сайта нет открытого API), извлечения информации и сохранения в базу данных. В настоящее время я использую красивый суп для написания кода следующим образом:

discount_price_text = soup.select("#detail-main del.originPrice")[0].string;
discount_price = float(re.findall('[\d\.]+', discount_price_text)[0]);

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

В частности, существует ли какой-либо существующий "умный скребок", который может "угадать наилучшее усилие", даже если исходный селектор xpath/css больше не действителен?

Ответ 1

Страницы

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

Поддержание работоспособности - это нечто вроде художественной формы, основанной на определении и использовании селекторов.

В прошлом я включил свои собственные "двухступенчатые" селектора:

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

  • (retrieve) Второй этап затем несколько гибкий и извлекает данные из нужного элемента на странице.

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

Я часто использовал селектор xpath, и это действительно удивительно, с небольшой практикой, насколько гибкой вы можете быть с хорошим селектором, но все еще очень точным. Я уверен, что селектора css похожи. Это становится легче, чем больше семантический и "плоский" дизайн страницы.

Несколько важных вопросов для ответа:

  • Что вы ожидаете изменить на странице?

  • Что вы ожидаете остаться на странице?

При ответе на эти вопросы, более точным вы можете быть лучше, чем ваши селекторы могут стать.

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


EDIT: Маленький пример

Используя ваш сценарий, где нужный элемент находится в .content > .deal > .tag > .price, общий селектор .content .price очень "гибкий" относительно изменений страницы; но если, скажем, возникает ложноположительный элемент, мы можем не желать извлекать из этого нового элемента.

С помощью двухступенчатых селекторов мы можем указать менее общий, более негибкий первый этап, например .content > .deal, а затем второй, более общий этап, например .price, для извлечения конечного элемента с использованием запроса относительно результатов первый.

Итак, почему бы просто не использовать селектор типа .content > .deal .price?

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

Я не должен сказать, что это "лучшая" практика, но она хорошо работала.

Ответ 2

EDIT: К сожалению, теперь я вижу, что вы уже используете селектор CSS. Я думаю, что они дают лучший ответ на ваш вопрос. Нет, я не думаю, что есть лучший способ.

Однако иногда вы можете обнаружить, что легче идентифицировать данные без структуры. Например, если вы хотите скопировать цены, вы можете выполнить поиск по регулярному выражению, соответствующий цене (\$\s+[0-9.]+), вместо того, чтобы полагаться на структуру.


Лично, готовые библиотеки webscraping, которые я пробовал, оставляют что-то желание (mechanize, Scrapy и другие).

Я обычно переворачиваю свой собственный, используя:

  • urllib2 (стандартная библиотека),
  • lxml и
  • cssselect

cssselect позволяет вам использовать селектор CSS (точно так же, как jQuery), чтобы найти конкретные div, таблицы и т.д. Это действительно неоценимо.

Пример кода для получения первого вопроса с домашней страницы SO:

import urllib2
import urlparse
import cookielib

from lxml import etree
from lxml.cssselect import CSSSelector

post_data = None
url = 'http://www.stackoverflow.com'
cookie_jar = cookielib.CookieJar()
http_opener = urllib2.build_opener(
    urllib2.HTTPCookieProcessor(cookie_jar),
    urllib2.HTTPSHandler(debuglevel=0),
)
http_opener.addheaders = [
    ('User-Agent', 'Mozilla/5.0 (X11; Linux i686; rv:25.0) Gecko/20100101 Firefox/25.0'),
    ('Accept', 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8'),
]
fp = http_opener.open(url, post_data)
parser = etree.HTMLParser()
doc = etree.parse(fp, parser)

elem = CSSSelector('#question-mini-list > div:first-child > div.summary h3 a')(doc)
print elem[0].text

Конечно, вам не нужен cookiejar или пользовательский агент для эмуляции FireFox, однако я считаю, что я регулярно нуждаюсь в этом при очистке сайтов.

Ответ 3

Полностью не связанный с Python, а не автоматически, но я думаю, что лучшие шаблоны моего Xidel scraper имеют лучшую устойчивость.

Вы напишете это как:

<div id="detail-main"> 
   <del class="originPrice">
     {extract(., "[0-9.]+")} 
   </del>
</div>

Каждый элемент шаблона сопоставляется с элементами на веб-странице, и если они совпадают, выражения внутри {} оцениваются.

Дополнительные элементы на странице игнорируются, поэтому, если вы найдете правильный баланс включенных элементов и удаленных элементов, на шаблон не повлияют все незначительные изменения. Основные изменения, с другой стороны, вызовут соответствующий сбой, намного лучше, чем xpath/css, который просто вернет пустой набор. Затем вы можете изменить в шаблоне только измененные элементы, в идеальном случае вы можете напрямую применить разницу между старой/измененной страницей и шаблоном. В любом случае вам не нужно искать, какой селектор затронут, или обновить несколько селекторов для одного изменения, так как шаблон может содержать все запросы для одной страницы вместе.