面向对象封装
Published: 2021-05-22 15:25:53

在前文书写代码的时候，脚本已经实现了基本的功能，从一个HTML中提取出我们想要的信息。但是，这样写出的代码至少有以下问题：

- 可封装性差
- 接口定义不清楚
- 迭代困难

google的serp页面经常会遇到一些小调整，我们的代码必然要经常进行更新，那么我们就需要保证代码的简洁性。在这个项目中，我们把握第一个原则：面向对象编程。

那么什么是面向对象？首先看下面的代码：

```python
class GoogleSpider(object):
    def __get_total_page(self):
        pass

    def search_text(self):
        pass

    def search_videos(self):
        pass

    def search_wiki(self):
        pass
...
```

这个代码，无法是将我们刚刚写出的函数封装在一个GoogleSpider类中，看似是用了面向对象的写法，但实际上类中的方法和数据并没有发生关联，还需要再进一步。

前文中，我们的输出会输出爬取内容的内容，即`type`字段。

在这里，我们类中提前定义四种类型。

完整的面向对象代码，如下所示：

```python
class GoogleSpider(AttribDict):
    __exclude_keys__ = {'soup'}

    def __init__(self, soup: BeautifulSoup):
        self.videos = []
        self.wiki = []
        self.page = 0
        self.news = []
        self.main = []
        self.soup = soup

    def __get_total_page(self):
        """获取当前页面总数"""
        pages_ = self.soup.find('span', id='xjs').findAll('td')
        maxn = 0
        for p in pages_:
            try:
                if int(p.text) > maxn:
                    maxn = int(p.text)
            except:
                pass
        self.page = maxn
    
    
    def __search_main(self):
        """解析主要搜索结果"""

        # 获取所有的主要搜索结果
        result_containers = self.soup.findAll('div', class_='g')

        for container in result_containers:
            # title提取
            try:
                title = container.find('h3').text
                # 对应链接提取
                url = container.find('a')['href']
                # 对应描述提取
                des = container.find('span', class_='aCOpRe').text
                self.main.append({
                    'title': title,
                    'url': url,
                    'des': des,
                })
            except Exception:
                continue

    def __search_wiki(self):
        """解析wiki内容"""
        container = self.soup.find('div', class_='kp-wholepage')
        # 如果container为None，则返回空列表
        if container is None:
            return []
        # Title
        title = container.find('h2', attrs={'data-attrid': 'title'}).find('span').text
        # Subtitle
        try:
            subtitle = container.find(
                'div', attrs={'data-attrid': 'subtitle'}).text
        except AttributeError:
            subtitle = None
        # Description
        des = container.find('div', class_='kno-rdesc').find('span').text
        # 获取Wiki链接
        url = container.find('div', class_='kno-rdesc').find('a')['href']
        # Details内容
        try:
            # div.wp-ms对应不同的四个card
            table = container.findAll(
                'div', class_='wp-ms')[2].findAll('tr', class_='kno-nf-nr')[1:]
        except IndexError:
            table = []
        details = []
        
        for row in table:
            name = row.find('span').text.strip(': ')
            detail_ = row.findAll('span')[1:]
            detail = ''
            for _ in detail_:
                detail += _.text + ' '  # 以 key value的形式输出结果
            details.append({
                'name': name,
                'detail': detail.strip()
            })
        result = {
            'title': title,
            'subtitle': subtitle,
            'des': des,
            'url': url,
            'details': details,
        }
        self.wiki = [result]


    def __search_news(self):
        try:
            cards = self.soup.find('g-scrolling-carousel').findAll('g-inner-card')
        except AttributeError:
            return []
        
        for card in cards:
            title = card.find('div', role='heading').text
            href = card.find('a')['href']
            result = {
                'title': title,
                'href':href,
            }
            self.news.append(result)
        

    def __search_videos(self):
        try:
            cards = self.soup.find('div', id='search').findAll('div', class_='VibNM')
        except AttributeError:
            return []
        
        for card in cards:
            title = card.find('div', role='heading').text
            href = card.find('a')['href']
            result = {
                'title': title,
                'href':href,
            }
            self.videos.append(result)


    def search(self):
        self.__get_total_page()
        self.__search_main()
        self.__search_news()
        self.__search_videos()
        self.__search_wiki()

    
spider = GoogleSpider(soup)
spider.search()
```

可以看到，通过面向对象的封装之后，最直观的就是注释变少，也就是输入和输出都不需要显示指定了，其次是我们可以使用更加多的设计模式来进一步完善我们这个`GoogleSpider`类。