前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >python爬虫:爬取猫眼电影数据并存入数据库

python爬虫:爬取猫眼电影数据并存入数据库

作者头像
冰霜
发布2022-03-15 15:59:24
2.6K1
发布2022-03-15 15:59:24
举报
文章被收录于专栏:冰霜的软件测试技术分享

这一篇详细介绍一下如何使用beautifulsoup或正则表达式来提取网页中的信息。

目标网站:猫眼电影-->榜单-->Top100榜

预期效果:抓取Top100榜中的数据,并存储到mysql数据库

1. 分析网页源码

网页html内容如下

可以看出每部电影信息都包含在一对<dd>...</dd>标签中,

所以第一步可以通过beautifulsoup库解析出所有<dd>标签对,

然后再从<dd>标签对中依次解析排名所在的<i>标签,电影名所在的<p>标签,上映时间所在的<p>标签以及分数所在的<p>标签

2. 构造请求url

Top100榜的url:

https://maoyan.com/board/4

点击进入第2页:

https://maoyan.com/board/4?offset=10

点击进入第3页:

https://maoyan.com/board/4?offset=20

可以看到随着翻页,offset以10的倍数递增

所以可以设置起始url如下:

https://maoyan.com/board/4

定义一个变量offset来控制爬取页数

故拼接url为:

代码语言:javascript
复制
url = start_url + '?offset=' + str(10 * i)


3. 以第一页为例,提取信息

# coding: utf-8
# author: hmk

from bs4 import BeautifulSoup
import requests
import bs4

url = 'http://maoyan.com/board/4'
header = {"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8",
              "Accept-Encoding": "gzip, deflate, sdch",
              "Accept-Language": "zh-CN,zh;q=0.8",
              "Cache-Control": "max-age=0",
              "Connection": "keep-alive",
              "Host": "maoyan.com",
              "Referer": "http://maoyan.com/board",
              "Upgrade-Insecure-Requests": "1",
              "User-Agent": "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.75 Safari/537.36"}
r = requests.get(url, headers=header)
r.encoding = r.apparent_encoding
html = r.text
soup = BeautifulSoup(html, 'html.parser')
# print(soup.find_all('dd'))
list=[]  # 定义一个列表,保存所有电影数据,一定不要定义在循环里面,不然每次都会清空,最后只会留下最后一部电影的数据
for dd in soup.find_all('dd'):
    index = dd.i.string  # 电影排名
    # print(index)
    movie = dd.find('p', class_='name').string  # 电影名称
    # print(movie.string)
    release_times = dd.find('p', class_='releasetime')  # 上映时间
    release_time = release_times.string
    # print(release_time.string)
    s = dd.find('p', class_='score').contents   # 分数
    score = s[0].string+s[1].string  # 把分数的整数部分和小数部分拼接

    list.append([index,movie,release_time,score])  # 把每一部电影的排名、名称、上映时间、分数添加到一个列表,再追加到一个大列表
print(list)

上述代码的重点在于for循环中信息是如何提取,然后组合的,思路如下:

(1)先提取出页面中所有的<dd>标签对,通过for循环把每组<dd>标签赋给一个dd变量,每一个dd变量都是一个bs4元素的Tag对象;

(2)得到dd标签的返回对象后,可以直接使用find方法来提取dd标签的子标签

开始的时候陷入了一个误区,因为打印出的dd内容是标签元素,然后就想着能不能再把它传进beautifulsoup,

生成一个新的beautifulsoup对象,实际证明不行,因为dd的类型已经是<class 'bs4.element.Tag'>了,而之前传进去的html=r.text的类型是<class 'str'>,很明显不能这样干!!

所以想不通时就打印一下对象类型看看是啥

(3)提取排名

使用 dd.i.string,dd.i表示提取dd标签下的第一个i标签,刚好排名信息就在dd标签下的第一个i标签,加上.string,表示提取文本

(4)提取电影名称

代码语言:javascript
复制
使用 dd.find('p', class_='name').string
提取dd标签下class属性为name的p标签,因为电影名称就在这个p标签

(5)提取上映时间

使用 dd.find('p', class_='releasetime')

(6)提取分数

因为分数分为2部分,整数部分和小数部分,且分别属于一个p标签下的i标签,

这样用tag.contents方法(tag的 .contents 属性可以将tag的子节点以列表的方式输出),

然后再将2部分拼接形成完整分数,如下:

代码语言:javascript
复制
dd.find('p',class_='score').contents[0].string+dd.find('p', class_='score').contents[1].string

看一下上述代码打印的内容

打印dd,会把当前爬取页数的电影html中的所有<dd>标签全部获取到

dd的类型

其实通过beautiful获取的html标签数据,都是bs4.element.Tag,也就是bs4的Tag对象

有了dd标签的内容后,再分别提取排名、名称等信息就方便了

注意:

在运行这段代码时,提取分数那里(第32行),遇到了一个错误

因为这个榜单是每天会变化的,有时候榜单上的电影没有分数这个标签,如下

这样的话,会报如下错误

原因就是获取不到<p>标签(这个错误排查了半天,偶然多爬了几页数据才发现这个错误)

解决方法:

在爬取电影分数时,先判断下是否存在包含分数的<p>标签,如果包含,则抓取数据,如果不包含,则直接给出“暂无分数”

代码语言:javascript
复制
 if isinstance(dd.find('p', class_='score'), bs4.element.Tag):
        s = dd.find('p', class_='score').contents   # 分数
        #print(s)
        score = s[0].string + s[1].string  # 把分数的整数部分和小数部分拼接
    else:
        score = "暂无分数"

isinstance() 函数用来判断一个对象是否是一个已知的类型,

所以如果有class属性为score的p标签,那么就肯定是bs4.element.Tag对象,

则可以提取分数,否则表示没有对应的p标签,则定义分数为“暂无得分”

4. 将代码简单封装,并将数据插入到数据库

代码语言:javascript
复制
# coding: utf-8
# author: hmk

import requests
from bs4 import BeautifulSoup
import bs4
import pymysql.cursors


def get_html(url, header):
    try:
        r = requests.get(url=url, headers=header, timeout=20)
        r.encoding = r.apparent_encoding
        if r.status_code == 200:
            return r.text
        else:
            return None
    except:
        return None


def get_data(html, list_data):
    soup = BeautifulSoup(html, 'html.parser')
    dd = soup.find_all('dd')
    for t in dd:
        if isinstance(t, bs4.element.Tag):  # 判断t是否为bs4的tag对象(可能存在空格)
            ranking = t.i.string  # 排名
            movie = t.find('p', class_='name').string
            release_time= t.find('p', class_='releasetime').string
            if isinstance(t.find('p', class_='score'), bs4.element.Tag):
            """判断是否有class属性为score的p标签"""
                score = t.find('p', class_='score').contents[0].string + t.find('p', class_='score').contents[1].string
            else:
                score = "暂无得分"
            print(score)

            list_data.append([ranking, movie, release_time, score])


def write_sql(data):
    conn = pymysql.connect(host='localhost',
                           user='root',
                           password='123456',
                           db='test',
                           charset='utf8')
    cur = conn.cursor()

    for i in data:
        """这里的data参数是指处理后的列表数据(是一个大列表,包含所有电影信息,每个电影信息都存在各自的一个列表中;
        对大列表进行迭代,提取每组电影信息,这样提取到的每组电影信息都是一个小列表,然后就可以把每组电影信息写入数据库了)"""
        movie = i  # 每组电影信息,这里可以看做是准备插入数据库的每组电影数据
        sql = "insert into maoyan_movie(ranking, movie, release_time, score) values(%s, %s, %s, %s)"  # sql插入语句 插入数据时,注意数据库字段的类型及长度是否满足插入值得要求
        try:
            cur.execute(sql, movie)  # 执行sql语句,movie即是指要插入数据库的数据
            conn.commit()  # 插入完成后,不要忘记提交操作
            print('导入成功')
        except:
            print('导入失败')
    cur.close()  # 关闭游标
    conn.close()  # 关闭连接


def main():
    start_url = 'http://maoyan.com/board/4'
    depth = 2  # 爬取深度(翻页)
    header = {"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8",
              "Accept-Encoding": "gzip, deflate, sdch",
              "Accept-Language": "zh-CN,zh;q=0.8",
              "Cache-Control": "max-age=0",
              "Connection": "keep-alive",
              "Host": "maoyan.com",
              "Referer": "http://maoyan.com/board",
              "Upgrade-Insecure-Requests": "1",
              "User-Agent": "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.75 Safari/537.36"}

    for i in range(depth):
        url = start_url + '?offset=' + str(10 * i)
        html = get_html(url, header)
        list_data = []
        get_data(html, list_data)
        #print(list_data)
        write_sql(list_data)
        # print(list_data)


if __name__ == "__main__":
    main()

write_sql()函数负责将数据插入到mysql数据库,

这里需要注意的是对应表的字段类型和长度与数据相匹配

运行程序,到数据库查看结果

5. 使用正则表达式提取信息

主要是把 get_data()函数 处理数据修改一下

代码语言:javascript
复制
def get_data(html, list_data):
    pattern = re.compile(r'<dd>.*?<i.*?>(\d+)</i>.*?'  # 匹配电影排名
                         r'<p class="name"><a.*?data-val=".*?">(.*?)'  # 匹配电影名称
                         r'</a>.*?<p.*?class="releasetime">(.*?)</p>'  # 匹配上映时间
                         r'.*?<i.*?"integer">(.*?)</i>'  # 匹配分数的整数位
                         r'.*?<i.*?"fraction">(.*?)</i>.*?</dd>', re.S)  # 匹配分数小数位
    m = pattern.findall(html)
    print(m)
    for i in m:  # 因为匹配到的所有结果会以列表形式返回,每部电影信息以元组形式保存,所以可以迭代处理每组电影信息
        ranking = i[0]  # 提取一组电影信息中的排名
        movie = i[1]  # 提取一组电影信息中的名称
        release_time = i[2]  # 提取一组电影信息中的上映时间
        score = i[3] + i[4]  # 提取一组电影信息中的分数,这里把分数的整数部分和小数部分拼在一起
        list_data.append([ranking, movie, release_time, score])  # 每提取一组电影信息就放到一个列表中,同时追加到一个大列表里,这样最后得到的大列表就包含所有电影信息
本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2019-10-21,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 冰霜blog 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档