差异大的函数但处理方式相似的模式与设计

3 投票
2 回答
897 浏览
提问于 2025-04-16 07:26

我正在写一些Python代码,用来抓取网站的信息,最终我会有一堆定制的抓取程序,每个大约50行,专门从特定的网站提取特定的信息。

我程序的第一版是一个很大的文件,它接受一个网站作为参数,如果它能识别这个网站并且有相应的抓取代码,就会抓取这个网站(通过一个很大的条件语句来检查它是否认识这个网站)。

显然,这样的设计不是很好,所以我想把这些定制的抓取函数放到自己的文件或类里,然后写一个小脚本,可以通过名字来调用它们。例如:

scrape.py --site google

我希望有一个类似这样的文件结构:

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

我还没有完全掌握面向对象编程,但我意识到这是需要用到的,我想要的可能是一种常见的面向对象模式。

有没有人能帮我把这段代码重构得更好一些?

PS - 我看过Scrapy,但由于各种原因,它并不完全符合我的需求。
PPS - 我实际上不是在抓取搜索网站,而是在抓取美国法院的网站。

2 个回答

1

你的重构方法正是我想要的。下面是我解决这个问题的思路。

第一步

我会在网站目录下的所有文件里,比如 google.py、yahoo.py 等,创建一个叫做 ScrapeHandler 的函数。

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(....) 
5

你可以把代码放在一个类里面,这个类里有一个叫做 __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()

这样做的好处是你不需要对 Scraper 类进行任何修改。如果你需要用不同的方法让服务器与你的抓取工具沟通,那么你可以在每个模块里创建一个 Downloader 类,使用方式和 Parser 类一样。如果你有两个或更多的解析器做同样的事情,只需在一个单独的模块中定义一个通用解析器,然后在每个需要它的网站模块中导入。或者你可以通过子类化来进行一些调整。由于不知道你是如何下载和解析网站的,所以很难给出更具体的建议。

我觉得你可能需要问几个问题,才能把所有细节搞清楚,但这将是一个很好的学习经历。

撰写回答