Шаблон и дизайн для функций, которые сильно отличаются друг от друга, но обрабатываются аналогично - PullRequest
2 голосов
/ 22 ноября 2010

Я пишу некоторый код Python для очистки веб-сайтов, и в итоге я собираюсь получить растущую коллекцию пользовательских скребков, каждая длиной около 50 строк, специально предназначенных для извлечения конкретной информации с определенного веб-сайта.

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

Очевидно, что это не очень хороший дизайн, поэтому я хотел бы перетащить пользовательские функции очистки в свои собственные файлы / классы и создать небольшой скрипт, который я могу использовать для вызова их по имени. Например:

scrape.py --site google

И я хотел бы, чтобы структура файла была похожа на:

scrape.py
sites/
    google.py
    yahoo.py
    ...
    bing.py

Я еще не освоил ориентацию объекта, но я понимаю, что это требует его, и что то, что я ищу, - это, вероятно, обычный шаблон ОО.

Любая помощь в правильном рефакторинге кода?

PS - Я смотрел на Scrapy, и это не совсем то, что мне нужно по разным причинам.
PPS - Я на самом деле не чищу сайты поиска, а чищу сайты судов США.

Ответы [ 2 ]

4 голосов
/ 22 ноября 2010

Вы можете поместить код в класс с помощью метода __init__ для настройки всего, метода _download для подключения к сайту и его загрузки, метода _store для сохранения результатов и runспособ связать все это вместе, например так:

class Scraper(object):
    def __init__(self, parser, page_generator):
        self._parser = parser
        self._pages = pages

    def _download(self, page):
        # do whatever you're already doing to download it
        return html

    def _store(self, data):
        # Do whatever you're already doing to store the data

    def run(self):
        for page in pages:
            html = self._download(page)
            data = self._parser.parse(html)
            self._store(data)

Этот класс может находиться в вашем файле parser.py.

В каждом из файлов вашего сайта поместите две вещи.

class Parser(object):
    def parse(html):
        # All of your rules go here

def pages(some, args, if_, you, need, them): # but they should be the same for all files
    return a_list_of_pages_or_generator

Затем вы можете настроить файл python.py с помощью следующей функции:

def get_scraper(name):
    mod = __import__(name)

    parser = mod.Parser()
    pages = mod.pages() # Pass whatever args you need to figure out the urls

    return Scraper(parser, pages)

Затем вы можете использовать его как

scraper = get_scraper('google')
scraper.run()

Делая этоПреимущество way заключается в том, что вам не нужно вносить какие-либо изменения в класс Scraper.Если вам нужно сделать разные трюки, чтобы заставить серверы общаться с вашим скребком, то вы можете создать класс Downloader в каждом модуле и использовать его так же, как класс Parser.Если у вас есть два или более анализатора, которые выполняют одно и то же, просто определите их как универсальный анализатор в отдельном модуле и импортируйте его в модуль каждого сайта, который в этом нуждается.Или подкласс это сделать твики.Не зная, как вы скачиваете и анализируете сайты, трудно быть более конкретным.

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

1 голос
/ 22 ноября 2010

Ваша техника рефакторинга - это то, как я бы поступил. Вот как я смотрю на реализацию этой проблемы.

Первый

Я бы создал одну функцию ScrapeHandler во всех файлах внутри каталога сайта - google.py, yahoo.py и т. Д.

def ScrapeHandler(...):
    ...

Второй

Я бы создал __init__.py в каталоге сайтов со следующим содержимым.

scrapers = ["google", "yahoo", ...]

Третий

В основном файле scrape.py я бы загружал скребок во время выполнения, чтобы выбрать подходящую логику очистки.

from sites import scrapers
all_scrapers = {}
......
# Load all scrapers
for scraper_name in scrapers:
    all_scrapers[scraper_name] = __import__('%s.%s' % (sites.__name__, scraper_name), fromlist=[scraper_name], level=0)
# get the input on what to scrape via command line etc
scraper_name = ..
assert scraper_name not in scrapers
# call the function based on name 
scrapeHandler = all_scrapers.get(scraper_name, None)
if scrapeHandler is not None:
    scrapeHandler(....) 
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...