Я не занимаюсь этим регулярно. Раз в 4-6 месяцев мне требуется распарсить какую-нибудь страничку или насобирать данных. Решил собрать мысли о том, как делать это легко в одну кучу.

Я использую для этих целей python3.6, requests и BeautifulSoup - и они неплохи.

Я используйте многоуровневую систему парсинга, о которой и пойдёт речь.

Сбор сырых данных -> разбор сырых данных -> подчистка данных -> использование данных.

1. Сперва пишем минимальную итерацию по нужным объектам.

for item in soup.find_all('blabla'):
    print(item)
    exit(1)

Добавляйте дебаговые принты, передвигайте exit после первого же разобранного элемента.

2. Затем добавляем кэширование.

Это нужно по двум причинам:

  1. собирать таким образом данные, ну, не самое приличное дело
  2. сетевые запросы - дело долгое и если вам нужно больше 40 страниц, лучше их закэшировать локально

Сделать это можно создав враппер вокруг requests:

endpoint = 'https://blabla.bla/'

def get(link):
    """ caching request """
    path = link.replace(endpoint, '.')
    if not os.path.exists(path):
        directory = os.path.dirname(path)
        if not os.path.exists(directory):
            os.makedirs(directory)
        with open(path, 'w') as fd:
            fd.write(requests.get(link).content.decode('utf-8'))
    with open(path) as fd:
        return fd.read()

Само собой обычные текстовые файлы - не обязательно, можете пихать это в редис, можете записывать на бумажку. Суть в том, что потом вы поменяете парсер и вам не надо будет заново DoSить сайт с данными своими дурацкими запросами. Инвалидация такого кэша делается легко - просто удаляем весь кэш к чёрту.

И только затем добавляем складывание данных в машинно-читаемый вид. Более того, я обычно делю это на две части.

3. Структурирование данных

Просто парсю страницу, создаю dict в который кладу то, что смог выдернуть из страницы как есть. Что не смог достать - заменяю дефолтными (заведомо плохими) значениями. Иногда для удобства формирование такого дикта удобно положить в класс объекта. Зачем - можно добавить строковое представление для отладки (__str__) чтобы просто print(Blah(link)), легче декомпозировать пухнущую функцию parse().

class Blah(object):
    def __init__(self, link):
        self.data = self.parse(link)

    def parse(self):
        raw_data = get(link)
        soup = BeautifulSoup(raw_data, "html.parser")
        return {
            'xx': soup.xx,
            'yy': soup.yy,
        }

    def __str__(self):
        json.dumps(self.data, indent=2)

4. Очистка данных

Приведение данных в нормальную форму. “3,20GHz” превращаются в 3,20 чтобы мы могли сортировать и фильтровать это после импорта в табличный процессор.

Чем дополнить статью в будущем

  • Упрощение дебага добавлянием click для передачи аргументов и создание удобного cli