BUG-Fly BUG-Fly
  • 首页
  • BUG-EXP
  • 编程开发
  • 电脑评测
  • 生活分享
  • 友情链接
  • Fly全站协议声明
首页 › Python › Python网络爬虫实战——爬取猫眼TOP100

Python网络爬虫实战——爬取猫眼TOP100

BUG-Fly
5 年前⦁置顶

一、综述

这是站长的第一个Python网络爬虫项目实战,利用Python的requests库和正规则库对猫眼网站的Top100排行进行爬取。涉及到的关键知识点有四点:

  1. Python 中 requests 库的使用。
  2. 利用 re 库(正规则库)对请求到的网页进行解析,并提取目标内容。
  3. json格式文件存储和读取方法。
  4. 了解重构在开发中的作用。

二、准备工作

开始之前我们需检查 requests 库是否已经正确安装好了。如若没有请按照以下要求进行安装和检查:

安装:在终端中输入
pip3 install requests
检查是否正确安装:
fly@mazhixiangs-MBP ~ % python3
Python 3.7.6 (default, Dec 27 2019, 09:51:21) 
[Clang 11.0.0 (clang-1100.0.33.16)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> import requests
>>> 

三、抓取分析

抓取的目标站点 url = 'https://maoyan.com/board/4'访问页面如下图所示:

Python网络爬虫实战——爬取猫眼TOP100-BUG-Fly

我们可以看到页面进行了分页,那么这是一种反爬措施。下面我们分析一下:

Python网络爬虫实战——爬取猫眼TOP100-BUG-Fly

当点击第二页时,页面的URL发生了变化:url2 = 'https://maoyan.com/board/4?offset=10'比之前的URL多了一个参数:params = 'offset=10',而且页面显示的排行榜的11-20名的电影。继续点击第三页:url3 = 'https://maoyan.com/board/4?offset=20'参数offset=30,而显示的是21-30名的电影。

由此可以做出总结:

猜想:
offset这个参数代表偏移量,如若offset=n,(n = 0,10,20,30……)则相应页面显示的电影范围应为:(n+1,n+10)。
验证:
url = 'https://maoyan.com/board/4'显示的是1-10的电影,则可推出offset=0,则不显示;
url2 = 'https://maoyan.com/board/4?offset=10'显示的是11-20的电影,则可推出offset=10;
url3 = 'https://maoyan.com/board/4?offset=20'显示的是21-30的电影,则可推出offset=20;

由此可以看出我们的推测是正确的。这样抓取这Top100的电影只需将offset循环到90即可。抓取不同的页面后再用正则表达式处理即可。下面我们实现抓取一页的代码:

四、抓取一页

import requests
from requests.exceptions import RequestException
def get_one_page(url):
    try:
        headers = {
            'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.117 Safari/537.36',
            'Cookie': '__mta=20281991.1579054760901.1579064616380.1579065114783.4; uuid_n_v=v1; uuid=6F3F3ED0373D11EA807E4F280ED9928C092CC249433244F59FBE8669FCFA2E50; _csrf=bc2c69be4d336bfd2624b0c6f6f1624c87f6bb56cf5858cf47f2ef512c1f0a15; mojo-uuid=d01a035c04286ed76b4910f8739e3c7d; Hm_lvt_703e94591e87be68cc8da0da7cbd0be2=1579004882,1579054760; _lxsdk_cuid=16fa6ff832ec8-0d6373886d4491-39627c0f-1fa400-16fa6ff832ec8; _lxsdk=6F3F3ED0373D11EA807E4F280ED9928C092CC249433244F59FBE8669FCFA2E50; mojo-session-id={"id":"bfb6d2e157ffc93b13c24914f1408623","time":1579064614757}; mojo-trace-id=4; Hm_lpvt_703e94591e87be68cc8da0da7cbd0be2=1579065114; _lxsdk_s=16fa795df6a-049-42-4b5%7C%7C5'
        }
        response = requests.get(url, headers=headers)
        if response.status_code == 200:
            return response.text
        return None
    except RequestException:
        return None
def main():
    url = 'https://maoyan.com/board/4'  
    html = get_one_page(url)
    print(html)
main()

在这一页中实现了get_one_page()方法参数为URL。这样就可以得到第一页的网页源代码了,下面我们需要对网页进行解析,提取有用信息。

五、解析一页源代码(正则表达式,正则库的使用)

下面我们来观察下页面的结构:

Python网络爬虫实战——爬取猫眼TOP100-BUG-Fly

可以发现一部电影的信息包含在 dd 节点内,下面我们用正则表达式来提取各部分信息:

  1. 排名信息:包含在 class 为board-index的 i 节点内 用非贪婪的方式来进行匹配
    <dd>.*?board-index.*?>(.*?)</i>
  2. 图片信息:可以看到后面有 a 节点 a 节点里面又包含两个 img 节点。第二个 img 节点的 data-src 属性是图片的链接,我们来匹配这个属性。改写如下:
    <dd>.*?board-index.*?>(.*?)</i>.*?data-src="(.*?)"
  3. 电影名称:它在后面的 p 节点内,class 为 name 可利用 name 作为标志进一步来提取后面 a 节点内的电影名称。改写如下:
    <dd>.*?board-index.*?>(.*?)</i>.*?data-src="(.*?)".*?name.*?a.*?>(.*?)</a>
  4. 主演、发布时间、评分等内容:
    <dd>.*?board-index.*?>(\d+)</i>.*?data-src="(.*?)".*?name"><a' + '.*?>(.*?)</a>.*?star">(.*?)</p>.*?releasetime">(.*?)</p>'+ '.*?integer">(.*?)</i>.*?fraction">(.*?)</i>.*?</dd>

解析代码如下:

def parse_one_page(html):
    pattern = re.compile('<dd>.*?board-index.*?>(\d+)</i>.*?data-src="(.*?)".*?name"><a'
                         + '.*?>(.*?)</a>.*?star">(.*?)</p>.*?releasetime">(.*?)</p>'
                         + '.*?integer">(.*?)</i>.*?fraction">(.*?)</i>.*?</dd>', re.S)
    items = re.findall(pattern, html)
    for item in items:
        yield {
            'index': item[0],
            'image': item[1],
            'title': item[2],
            'actor': item[3].strip()[3:],
            'time': item[4].strip()[5:],
            'score': item[5] + item[6]
        }

说明:

  1. re.S:为正则修饰符,匹配换行符。
  2. .findall():该方法会搜索整个字符串,然后返回匹配正则表达式的所有内容,在本段中会扫描"html"然后匹配结果。
  3. .comile():该方法会将正规则字符串编译成正则表达式对象,以便在后面的匹配中复用。在本段中因为我们要匹配10条这样的信息所以用到了它。
  4. yield关键字:yield就是 return 返回一个值,并且记住这个返回的位置,下次迭代就从这个位置后开始。这个关键字给出一个例子:
    def fun():
    print("starting…")
    while True:
    res = yield 10
    print("req:",req)
    n = fun()
    print(next(n))
    print("*
    "10)
    print(next(n))

    输出:
    starting…
    4
    **********
    res: None
    4

    解释:
    1.程序开始执行以后,因为fun函数中有yield关键字,所以fun函数并不会真的执行,而是先得到一个生成器n(相当于一个对象).
    2.直到调用next方法,fun函数正式开始执行,先执行fun函数中的print方法,然后进入while循环.
    3.程序遇到yield关键字,然后把yield想想成return,return了一个10之后,程序停止,并没有执行赋值给req操作,此时next(n)语句执行完成,所以输出的前两行(第一个是while上面的print的结果,第二个是return出的结果)是执行print(next(n))的结果.
    4.程序执行print("*"*10),输出10个*.
    5.又开始执行下面的print(next(n)),这个时候和上面那个差不多,不过不同的是,这个时候是从刚才那个next程序停止的地方开始执行的,也就是要执行res的赋值操作,这时候要注意,这个时候赋值操作的右边是没有值的(因为刚才那个是return出去了,并没有给赋值操作的左边传参数),所以这个时候req赋值是None,所以接着下面的输出就是req:None.
    6.程序会继续在while里执行,又一次碰到yield,这个时候同样return 出10,然后程序停止,print函数输出的4就是这次return出的10.

六、写入文件

def write_to_file(content):
    with open('result.txt', 'a', encoding='utf-8') as f:
        f.write(json.dumps(content, ensure_ascii=False) + '\n')

说明:

  1. 将提取结果写入文件中.
  2. 利用json库的dumps()方法实现字典的序列化,并指定ensure_scii参数为False,此举保证了输出结果是中文形式而不是Unicode编码.
  3. 调用write_to_file()方法实现字典写入文件的过程.

七、整合代码、多页爬取

完整代码如下:

import json
import requests
from requests.exceptions import RequestException
import re
import time


def get_one_page(url):
    try:
        headers = {
            'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.117 Safari/537.36',
            'Cookie': '__mta=20281991.1579054760901.1579064616380.1579065114783.4; uuid_n_v=v1; uuid=6F3F3ED0373D11EA807E4F280ED9928C092CC249433244F59FBE8669FCFA2E50; _csrf=bc2c69be4d336bfd2624b0c6f6f1624c87f6bb56cf5858cf47f2ef512c1f0a15; mojo-uuid=d01a035c04286ed76b4910f8739e3c7d; Hm_lvt_703e94591e87be68cc8da0da7cbd0be2=1579004882,1579054760; _lxsdk_cuid=16fa6ff832ec8-0d6373886d4491-39627c0f-1fa400-16fa6ff832ec8; _lxsdk=6F3F3ED0373D11EA807E4F280ED9928C092CC249433244F59FBE8669FCFA2E50; mojo-session-id={"id":"bfb6d2e157ffc93b13c24914f1408623","time":1579064614757}; mojo-trace-id=4; Hm_lpvt_703e94591e87be68cc8da0da7cbd0be2=1579065114; _lxsdk_s=16fa795df6a-049-42-4b5%7C%7C5'
        
        }
        response = requests.get(url, headers=headers)
        if response.status_code == 200:
            return response.text
        return None
    except RequestException:
        return None


def parse_one_page(html):
    pattern = re.compile('<dd>.*?board-index.*?>(\d+)</i>.*?data-src="(.*?)".*?name"><a'
                         + '.*?>(.*?)</a>.*?star">(.*?)</p>.*?releasetime">(.*?)</p>'
                         + '.*?integer">(.*?)</i>.*?fraction">(.*?)</i>.*?</dd>', re.S)
    items = re.findall(pattern, html)
    for item in items:
        yield {
            'index': item[0],
            'image': item[1],
            'title': item[2],
            'actor': item[3].strip()[3:],
            'time': item[4].strip()[5:],
            'score': item[5] + item[6]
        }


def write_to_file(content):
    with open('result.txt', 'a', encoding='utf-8') as f:
        f.write(json.dumps(content, ensure_ascii=False) + '\n')


def main(offset):
    url = 'http://maoyan.com/board/4?offset=' + str(offset)
    html = get_one_page(url)
    for item in parse_one_page(html):
        print(item)
        write_to_file(item)


if __name__ == '__main__':
    for i in range(10):
        main(offset=i * 10)
        time.sleep(1)

八、重构代码

在大型项目中,经常需要在添加新代码前重构既有代码。重构旨在简化既有代码的结构,使其更容易扩展。前边的整合代码中我们已经把各部分功能实现了函数模块化,那么下面我们就把各部分抽离出来,把这些函数封装为一个个的模块,并创建一个"maoyan_main.py"模块然后把其他模块集成到这个模块。

maoyan_main.py

import time
import maoyan_post
import maoyan_save
import maoyan_parse 

def main(offset):
    url = 'http://maoyan.com/board/4?offset=' + str(offset)
    html = maoyan_post.get_one_page(url)
    parse = maoyan_parse.parse_one_page(html)
    for item in parse:
        
        maoyan_save.write_to_file(item)


if __name__ == '__main__':
    for i in range(10):
        main(offset=i * 10)
        time.sleep(1)

maoyan_post.py

import requests
from requests.exceptions import RequestException
def get_one_page(url):
    try:
        headers = {
            'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.117 Safari/537.36'
            'Cookie': '__mta=20281991.1579054760901.1579064616380.1579065114783.4; uuid_n_v=v1; uuid=6F3F3ED0373D11EA807E4F280ED9928C092CC249433244F59FBE8669FCFA2E50; _csrf=bc2c69be4d336bfd2624b0c6f6f1624c87f6bb56cf5858cf47f2ef512c1f0a15; mojo-uuid=d01a035c04286ed76b4910f8739e3c7d; Hm_lvt_703e94591e87be68cc8da0da7cbd0be2=1579004882,1579054760; _lxsdk_cuid=16fa6ff832ec8-0d6373886d4491-39627c0f-1fa400-16fa6ff832ec8; _lxsdk=6F3F3ED0373D11EA807E4F280ED9928C092CC249433244F59FBE8669FCFA2E50; mojo-session-id={"id":"bfb6d2e157ffc93b13c24914f1408623","time":1579064614757}; mojo-trace-id=4; Hm_lpvt_703e94591e87be68cc8da0da7cbd0be2=1579065114; _lxsdk_s=16fa795df6a-049-42-4b5%7C%7C5'
        }
        response = requests.get(url, headers=headers)
        if response.status_code == 200:
            return response.text
        return None
    except RequestException:
        return None

maoyan_parse.py

import re
def parse_one_page(html):
    pattern = re.compile('<dd>.*?board-index.*?>(\d+)</i>.*?data-src="(.*?)".*?name"><a'
                         + '.*?>(.*?)</a>.*?star">(.*?)</p>.*?releasetime">(.*?)</p>'
                         + '.*?integer">(.*?)</i>.*?fraction">(.*?)</i>.*?</dd>', re.S)
    items = re.findall(pattern, html)
    for item in items:
        yield {
            'index': item[0],
            'image': item[1],
            'title': item[2],
            'actor': item[3].strip()[3:],
            'time': item[4].strip()[5:],
            'score': item[5] + item[6]
        }

maoyan_save.py

import json
def write_to_file(content):
    with open('result.txt', 'a', encoding='utf-8') as f:
        f.write(json.dumps(content, ensure_ascii=False) + '\n')

这样一来既避免了项目源代码更长,并使得项目的逻辑更容易理解且也使得项目有层次体系,使得后期扩展维护更加容易。

这就是本次项目的所有内容,预告一下:下期实战项目是天科大教务系统,主要实现两个功能:

1.查询成绩
2.选课功能
即飞跃产品的第4个产品:飞跃校园|Fly-College
PythonPython实战项目
6
0
BUG-Fly
写BUG飞起的Coder
斐波那契数列(Fibonacci)
上一篇
我爱你Python——情人节快乐
下一篇

评论 (0)

请登录以参与评论
现在登录
    发表评论

猜你喜欢

  • 如何优雅地在 Chrome Headless 模式下触发网站的 favicon 请求
  • Python 函数
  • Python内置数据结构——列表(list)
  • Python基础语法

词云

2020 (1) Flask (1) Go (1) JS逆向 (1) Linux (1) Playwright (1) PySide2开发 (1) Python (13) Python实战项目 (5) 固原一中 (1) 国庆70周年 (1) 开源 (1) 数据结构和算法 (2) 数组 (1) 新年贺词 (1) 新月诗刊社 (3) 电脑评测 (3) 软件教程 (3) 雨雯公益 (1) 音乐 (3)

BUG-Fly

写BUG飞起的Coder
34
文章
5
评论
324
获赞
  • 首页
  • 友情链接
Copyright © 2019-08-20-2025 BUG-Fly. Designed by BUG-Fly.

Fly小站已经运行:

津ICP备19007312号
技术基佬基地: KRUNK ZHOU Legna 科技
  • Python13
  • Python实战项目5
  • 新月诗刊社3
  • 音乐3
  • 电脑评测3
  • 首页
  • BUG-EXP
  • 编程开发
  • 电脑评测
  • 生活分享
  • 友情链接
  • Fly全站协议声明