2.3 使用Requests和正则表达式爬取猫眼电影Top 100排行榜

打开猫眼电影网站,进入到榜单页面,观察发现每页有10个电影,点击下一页时网站地址会添加一个参数offset,参数值为10,20,30, ... , 100: 如http://maoyan.com/board/4?offset=20

以此为入口地址,首先进行单页解析。按照如下步骤进行获取网站基本信息。

  1. 使用Chrome浏览器打开榜单页面http://maoyan.com/board/4

  2. 鼠标右键检查,可以查看网络元素与浏览器渲染结果

  3. 选中Network标签,刷新网站,观察到浏览器与服务器之间有很多请求与响应,包括主要页面与cssjs文件等等

  4. 从服务器的主要Response中获取Request headers信息,用于爬虫中构造headers

  1. Network页签下的Response页获取服务器的原始响应信息,一定要从此处获取而不是Element处获取,因为Element中的源码可能经过 JavaScript 的操作而和原始请求的不同。在Response中页签中找到需要获取的信息,如:电影名,海报链接,演员,上映时间地点,排名,打分

观察到每个电影均以<dd>...</dd>标签来包含。

<dd>
                        <i class="board-index board-index-21">21</i>
    <a href="/films/1249" title="黑客帝国" class="image-link" data-act="boarditem-click" data-val="{movieId:1249}">
      <img src="//ms0.meituan.net/mywww/image/loading_2.e3d934bf.png" alt="" class="poster-default" />
      <img data-src="http://p1.meituan.net/movie/d981a12f59d3cc92ff666094404ad8f0211220.jpg@160w_220h_1e_1c" alt="黑客帝国" class="board-img" />
    </a>
    <div class="board-item-main">
      <div class="board-item-content">
              <div class="movie-item-info">
        <p class="name"><a href="/films/1249" title="黑客帝国" data-act="boarditem-click" data-val="{movieId:1249}">黑客帝国</a></p>
        <p class="star">
                主演:基努·里维斯,凯瑞-安·莫斯,劳伦斯·菲什伯恩
        </p>
<p class="releasetime">上映时间:2000-01-14</p>    </div>
    <div class="movie-item-number score-num">
<p class="score"><i class="integer">9.</i><i class="fraction">0</i></p>        
    </div>

      </div>
    </div>

                </dd>
  1. 利用正则表达式(.*?)过滤出各个元素.

<dd>.*?board-index.*?">(.*?)</i>.*?title="(.*?)".*?data-src="(.*?)".*?movie-item-info.*?star">(.*?)</p>.*?releasetime">(.*?)</p>.*?integer">(.*?)</i>.*?fraction">(.*?)</i>.*?</dd>

解析一下这个正则表达式:

  • <dd>.*?board-index表示找到以<dd>开头的字符串后,继续往后匹配,.*?表示匹配任意个任意字符,直到第一次(问号表示的非贪婪模式)遇到board-index字符串后结束这段匹配。

  • .*?">(.*?)</i>表示的是,在前面的匹配找到后,继续进行匹配任意个任意字符,直到第一次遇到">字符串,通过(.*?)</i>匹配提取括号中的任意个任意字符串,直到第一次遇到</i>字符串。

上面的这两段能够匹配出 <dd> <i class="board-index board-index-21">21</i>,提取出排名21字符串。

  • 继续匹配,后面的正则表达式.*?title="(.*?)" 表示匹配任意个任意字符,直到第一次遇到title="字符串,将"符号后面的字符串使用(.*?)"进行匹配,提取第二个"之前的字符串。

即能匹配提取title="黑客帝国"中的黑客帝国

  • 继续匹配,.*?data-src="(.*?)",表示匹配任意个任意字符,直到第一次遇到data-src="字符串,使用(.*?)提取其后面的字符串。

  • 后续匹配类似,即使用.*?匹配任意个任意字符,并使用非贪婪模式,需要提取的字符串使用()括起来。

基于上面的分析,可以编写网页获取函数与解析函数。

import requests
import re

#导入urllib库用于做异常处理
import urllib
from urllib import error

def get_one_page(url):
    headers = {
    #在浏览器中观察获取Cookie,host,user-agent参数    
    'Cookie': 'uuid_n_v=v1; uuid=6266F950913511E8BAE959978C7B0D0473005491BEDE4F03918D808909FBCD63; _csrf=ac998ee17cc0a0a19ead8ae7bad340ccd342180e2588ba8b326ee6d6b4deac25; _lxsdk_cuid=164d92c0d5a25-0e046d8f7973c-16386952-100200-164d92c0d5bc8; _lxsdk=6266F950913511E8BAE959978C7B0D0473005491BEDE4F03918D808909FBCD63; __mta=152329862.1532651901646.1532653173231.1532653961468.14; _lxsdk_s=164d92c0d5c-908-59-aa6%7C%7C56',
    'Host': 'maoyan.com',
    'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.99 Safari/537.36'
    }
    #使用try处理异常
    try:
        response = requests.get(url,headers=headers)
        return response.text
    except error.HTTPError as err:
        print(err.reason, err.code, err.headers, sep='\n')
        return None
    except urllib.error.URLError as err:
        print(err.reason)
        return None

def parse_one_page(html):
    #定义正则表达式
    reg = r'<dd>.*?board-index.*?">(.*?)</i>.*?title="(.*?)".*?data-src="(.*?)".*?movie-item-info.*?star">(.*?)</p>.*?releasetime">(.*?)</p>.*?integer">(.*?)</i>.*?fraction">(.*?)</i>.*?</dd>'
    #使用re模块匹配查询出所有匹配正则的字符串,返回字符串列表
    results = re.findall(reg,html,re.S)
    # print(results)
    for item in results:
        # 使用yield进行迭代返回
        yield {
            '排名': item[0],
            '电影': item[1],
            '海报链接': item[2],
            # 添加条件判断语句,增加程序的健壮性,只有当item[3]长度大于3时才从第三个开始获取
            '演员': item[3].strip()[3:] if len(item[3])>3 else '',
            # 添加条件判断语句,增加程序的健壮性,只有当item[4]长度大于5时才能获取上映时间
            '上映时间': item[4].strip()[5:15] if len(item[4]) > 5 else '',
            # 通过正则表达式获取括号()里的字符串,且添加条件判断语句只有匹配到了才获取其中的字符串,
            '上映地点': re.findall(r'\((.*?)\)',item[4],re.S)[0] if len(re.findall(r'\((.*?)\)',item[4],re.S))>0 else '',
            # 将原有的字符串变成数字,将评分前半段去掉点号,小数位除以10
            '猫眼评分': int(item[5].strip('.'))+int(item[6])/10
        }

#保存结果到文本文件        
def write_to_json(content):
    #content 参数就是一部电影的提取结果,是一个字典。
    with open('result.txt', 'a', encoding='utf-8') as f:
        f.write(json.dumps(content, ensure_ascii=False) + '\n')

#抓取单个网页,并解析        
def single_page(offset):
    url = 'http://maoyan.com/board/4?offset='+str(offset*10)
    html = get_one_page(url)
    for item in parse_one_page(html):
        print(item)
        write_to_json(item)

if __name__ == '__main__':
    #抓取所有10页
    for page in range(10):
        single_page(page)

Last updated