页面内容提取
Published: 2021-05-22 05:28:54

## 页面内容结构

![image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/rtdcnjz1fkojmg1jh94k.png)
 
> 部分页面还有广告、推荐视频等模块

`ctrl+u`查看源码，如图：

![image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/381390l17bjp6mxu2ir6.png)

格式化后：

 ![image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/y150fthm5381jsolangi.png)
 

## 提取主要搜索结果

将搜索腾讯的内容源码保存在本地，命名为`tencen.html`，使用bs4包进行解析。
```python
with open('tencent.html', 'r', encoding='utf-8') as f:
    res = f.read()

soup = BeautifulSoup(res, 'html.parser')
print(soup.title.text)
```
返回title的结果：
![image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/lvxeibkc658pyqdelbcw.png)

我们首先提取主要的搜索结果，通过对代码的分析，google的主要搜索结果包裹在`div.g`中，如图。
![image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/kmr8orw3sw7jrtwln1zz.png)

标题对应是`h3`元素，点击的链接对应的是`a.href`，描述内容在`span.aC0pRe`内容中，就可以写出一个对主要搜索结果的解析函数：

```python
def search_result(soup:BeautifulSoup) -> list:
    """解析主要搜索结果

    Args:
        soup (BeautifulSoup): bs4对网页页面的解析结果

    Returns:
        list: 解析后的结果列表
    """
    # 获取所有的主要搜索结果
    result_containers = soup.findAll('div', class_='g')
    # 返回结果
    results = []

    for container in result_containers:
        # title提取
        try:
            title = container.find('h3').text
            # 对应链接提取
            url = container.find('a')['href']
            # 对应描述提取
            des = container.find('span', class_='aCOpRe').text
            results.append({
                'title': title,
                'url': url,
                'des': des,
                'type': 'result'
            })
        except Exception:
            continue
    return results
```
输出如下：
![image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/t3e6emycwen4byp0y4a0.png)

## 提取Wiki区域内容

wiki区域被包裹在"div.kp-wholepage"中，其中`h2, {data-attrid: title}`元素指向wiki区域的标题，`div, {data-attrid:subtitle}`指向wiki的副标题。
`div.kno-rdesc`中含有指向维基百科的链接和相关描述，分别存放在`a.href`和`span`元素中。大致结构如下图。

![image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/j733thxkr797wtj82kwp.png)

wiki区域中还有可能含有table元素，例如搜索"咖啡"，会显示其相关的成分，如图：

![image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/22vn5blmy6ia34i38nlq.png)

这个区域放在`(div.wp-ms)[2]`中的`tr.kno-nf-nr`元素中。于是，就可以写出解析wiki的对应函数。
 

```python
def search_wiki(soup:BeautifulSoup) -> list:
    """解析wiki内容

    Args:
        soup (BeautifulSoup): bs4对网页页面的解析结果

    Returns:
        list: 解析后的结果列表
    """
    container = 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,
        'type': 'wiki'
    }
    return [result]
```

输出如下：
![image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ajqqw65q9ryxba2ow6f8.png)

## 解析相关视频、搜索、页面数

对视频推荐的解析与上述类型，都是先定位好元素位置，再提取出相应的内容。视频提取google。 代码如下：

```python
def search_videos(soup: BeautifulSoup):
    try:
        cards = soup.find('div', id='search').findAll('div', class_='VibNM')
    except AttributeError:
        return []
    
    results = []
    for card in cards:
        title = card.find('div', role='heading').text
        href = card.find('a')['href']
        result = {
            'title': title,
            'href':href,
            'type': 'video'
        }
        results.append(result)
    return results


def get_total_page(soup: BeautifulSoup) -> int:
    """获取搜索页面数

    Args:
        response (requests.Response): the response requested to Google using requests

    Returns:
        int: the total page number (might be changing when increasing / decreasing the current page number)
    """
    pages_ = 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
    return maxn
```

最后，将结果进行聚合。

```python
results = []
results.extend(search_result(soup))
results.extend(search_wiki(soup))
results.extend(search_news(soup))
results.extend(search_videos(soup))
pages = get_total_page(soup)

print({
    'results': results,
    'pages': pages
})
```

## 总结

对google serp的解析流程大概是这样，但是这样的代码，并无法达到我们的要求。在下一节中，我们对代码进行一次重构，将会引出我们代码编写的第一个原则：面向对象编程。