天天看点

Python爬虫 爬取豆瓣电影TOP250Python爬虫 爬取豆瓣电影TOP250

Python爬虫 爬取豆瓣电影TOP250

最近在b站上学习了一下python的爬虫,实践爬取豆瓣的电影top250,现在对这两天的学习进行一下总结

主要分为三步:

  1. 爬取豆瓣top250的网页,并通过正则表达式将需要的信息摘取下来
  2. 将这些信息存入excel表格中
  3. 将这些信息存入MySQL数据库中(复习一下python对数据库的操作)

对豆瓣电影TOP250网页的爬取

进入豆瓣top250

Python爬虫 爬取豆瓣电影TOP250Python爬虫 爬取豆瓣电影TOP250

看它的URL

Python爬虫 爬取豆瓣电影TOP250Python爬虫 爬取豆瓣电影TOP250

可以发现start=0时是第一页,点击第二页的时候是start=25,根据这个规律就可以将全部10页的网页都爬取下来。

首先要导入几个python库,分别是urllib,BeautifulSoup,re,可以解决网络请求,网页解析和正则表达式提取所需内容的问题。

代码

from bs4 import BeautifulSoup
import re 
import urllib.request,urllib.error

def main():
    print("开始爬取数据")
    baseUrl = "https://movie.douban.com/top250?start="
    dataList = getData(baseUrl) #爬取数据
    print("爬取数据完成")

findLink = re.compile(r'<a href="(.*?)" target="_blank" rel="external nofollow"  target="_blank" rel="external nofollow" >') # 提取影片链接的正则表达式
findImgSrc = re.compile(r'<img.*src="(.*?)"',re.S) # 提取图片链接,re.S让换行符包含在字符中
findTitle = re.compile(r'<span class="title">(.*)</span>') # 提取影片名称
findRating = re.compile(r'<span class="rating_num" property="v:average">(.*)</span>') # 提取评分
findJudgeNum = re.compile(r'<span>(\d*)人评价</span>') # 评分人数
findInq = re.compile(r'<span class="inq">(.*)</span>') # 影片介绍
findBd = re.compile(r'<p class="">(.*?)</p>',re.S) # 影片背景
    
def getData(url):
    dataList = []
    for i in range(0,10):
        tmp_url = url + str(i*25)
        html = askURL(tmp_url)
        # 逐一解析网页
        soup = BeautifulSoup(html,"html.parser")
        for item in soup.find_all("div",class_="item"):
            item = str(item)
            data = []
            link = re.findall(findLink,item)[0] # 获取影片链接
            data.append(link)
            imgSrc = re.findall(findImgSrc,item)[0]
            data.append(imgSrc)
            title = re.findall(findTitle,item)
            if len(title) == 2:
                ctitle = title[0]
                data.append(ctitle)
                foreign_title = title[1].replace("/","")
                data.append(foreign_title)
            else:
                ctitle = title[0]
                foreign_title = " " # 没有外文名时留空
                data.append(ctitle)
                data.append(foreign_title)
            rating = re.findall(findRating,item)[0]
            data.append(rating)
            judgeNum = re.findall(findJudgeNum,item)[0]
            data.append(judgeNum)
            inq = re.findall(findInq,item)
            if len(inq) != 0:
                inq = inq[0].replace("。","")
                data.append(inq)
            else:
                inq = " "
                data.append(inq)
            bd = re.findall(findBd,item)[0]
            bd = re.sub(r'<br(\s+)?/>(\s+)?'," ",bd) # 去掉bd中的<br/>
            data.append(bd.strip()) # 去掉前后的空格
            dataList.append(data)
            
    return dataList

def askURL(url):
    head = {
            "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.105 Safari/537.36"
            }# 伪装成浏览器
    req = urllib.request.Request(url,headers=head)

    html = ""
    try:
        response = urllib.request.urlopen(req)
        html = response.read().decode("utf-8")# 得到网页
    except urllib.error.HTTPError as e:
        if hasattr(e,"code"):
            print(e.code)
        if hasattr(e,"reason"):
            print(e.reason)
    return html

if __name__ == "__main__":
    main()
           

分析

在main函数里创建一个getData函数来获取爬取到的网页数据,在getData函数里使用askURL函数得到每一个网页,在askURL里尤其要注意头部的伪装,将python爬虫程序伪装成浏览器访问样式。

当然豆瓣也使用了对同一ip访问过于频繁的过滤,所以如果访问的太过频繁被403了,可以考虑增加使用代理ip来伪装程序,我就在调试的过程中被ban了,不过由于不想买代理ip,并且网上找到的免费的代理ip全都没用,我就直接梯子开全局模式将https请求的ip改到别的地区了。

通过一次askURL可以得到一页25部电影的网页代码,然后先对豆瓣的这个网页代码分析一下,看看一会要提取的数据在哪些标签中,打开网页源码,找到包含电影信息的标签

<li>
            <div class="item">
                <div class="pic">
                    <em class="">1</em>
                    <a href="https://movie.douban.com/subject/1292052/">
                        <img width="100" alt="肖申克的救赎" src="https://img2.doubanio.com/view/photo/s_ratio_poster/public/p480747492.webp" class="">
                    </a>
                </div>
                <div class="info">
                    <div class="hd">
                        <a href="https://movie.douban.com/subject/1292052/" class="">
                            <span class="title">肖申克的救赎</span>
                                    <span class="title">&nbsp;/&nbsp;The Shawshank Redemption</span>
                                <span class="other">&nbsp;/&nbsp;月黑高飞(港)  /  刺激1995(台)</span>
                        </a>


                            <span class="playable">[可播放]</span>
                    </div>
                    <div class="bd">
                        <p class="">
                            导演: 弗兰克·德拉邦特 Frank Darabont&nbsp;&nbsp;&nbsp;主演: 蒂姆·罗宾斯 Tim Robbins /...<br>
                            1994&nbsp;/&nbsp;美国&nbsp;/&nbsp;犯罪 剧情
                        </p>

                        
                        <div class="star">
                                <span class="rating5-t"></span>
                                <span class="rating_num" property="v:average">9.7</span>
                                <span property="v:best" content="10.0"></span>
                                <span>2255328人评价</span>
                        </div>

                            <p class="quote">
                                <span class="inq">希望让人自由。</span>
                            </p>
                            

    <p>
        
        <span class="gact">
            <a href="https://movie.douban.com/wish/200078450/update?add=1292052" target="_blank" class="j a_collect_btn" name="sbtn-1292052-wish" rel="nofollow">想看</a>
        </span>&nbsp;&nbsp;
        
        <span class="gact">
            <a href="https://movie.douban.com/collection/200078450/update?add=1292052" target="_blank" class="j a_collect_btn" name="sbtn-1292052-collection" rel="nofollow">看过</a>
        </span>&nbsp;&nbsp;
    </p>

                    </div>
                </div>
            </div>
        </li>
           

这里只简述一下,源码就不全部粘贴了,可以发现每部电影都在<div class=“item”>中,于是只需要在得到的网页中把类是item的这一块提取出来就行了

soup = BeautifulSoup(html,"html.parser")
for item in soup.find_all("div",class_="item"):
           

也就是这两句的作用,将得到的网页通过html解析器来解析,然后找到div标签中类是item的所有字符,剩下的就是对这些字符的正则表达式提取了,使用re库即可很容易的办到,网上也有很多关于正则表达式的讲解,比如菜鸟教程,本次用到的基本就是懒惰匹配和贪婪匹配。

将这些信息都保存到二维列表dataList中,这样每一行都保存的就是一部电影的信息,至此,对250部电影信息的提取就结束了。

将电影信息保存到excel表中

就是将列表内容保存到excel中,使用python可以很方便的解决,只需要导入xlwt库就可以了。

代码

def main():
    print("开始爬取数据")
    baseUrl = "https://movie.douban.com/top250?start="
    dataList = getData(baseUrl) #爬取数据
    print("爬取数据完成")
    savePath = r'spider.xls'
    saveData(dataList,savePath) #保存数据到excel中
    
def saveData(dataList,savePath):
    book = xlwt.Workbook(encoding="utf-8")
    sheet = book.add_sheet('豆瓣电影top250',cell_overwrite_ok=True)
    col = ("影片链接","图片链接","影片中文名称","影片外文名","评分","评论人数","影片介绍","影片背景")
    for i in range(len(col)):
        sheet.write(0,i,col[i])
    for k in range(len(dataList)):
        data = dataList[k]
        for j in range(len(data)):
            sheet.write(k+1,j,data[j])
    book.save(savePath)
    
if __name__ == "__main__":
    main()

           

分析

主要注意的就是编码的问题,防止储存的时候出现乱码,当然把所有创建的文件都改成utf-8就完了。

保存后的结果:

Python爬虫 爬取豆瓣电影TOP250Python爬虫 爬取豆瓣电影TOP250

可以看出保存的结果非常成功,我发现这些top250的电影大部分都看过,不过大陆电影里没有路边野餐让我有点可惜,毕赣的这个电影给人的感觉也不输这些top,这里指路一下知乎里对它的介绍吧,《路边野餐》究竟讲了个什么故事?。

将信息保存到MySQL中

温习一下python链接mysql数据库的操作,将这些东西存进数据库里,当然和上面一样引入一个pymysql的库就能大大简化操作了。

代码

def main():
    print("开始爬取数据")
    baseUrl = "https://movie.douban.com/top250?start="
    dataList = getData(baseUrl) #爬取数据
    print("爬取数据完成")
    print(len(dataList))
    savePath = r'spider.xls'
    saveData(dataList,savePath) #保存数据到excel中
    print("保存数据到excel表中完成")
    sm = SaveMySQL("用户名","密码","数据库名",dataList) #保存数据到MySQL中
    sm.createTable()
    sm.insertData()
    print("保存数据到MySQL中完成")
    
class SaveMySQL:
    def __init__(self, user:str, password:str, database:str, dataList:List):
        self.user = user
        self.password = password
        self.database = database
        self.dataList = dataList

    def createTable(self):
        conn = pymysql.connect("localhost",self.user,self.password,self.database)
        cur = conn.cursor()
        sql1 = "drop table if exists moive_doubanTOP250;"
        sql2 = '''
            create table moive_doubanTOP250(
                id int primary key auto_increment,
                link varchar(255),
                imgsrc varchar(255),
                chinese_name varchar(100),
                foreign_name varchar(100),
                rating float,
                judgeNum int,
                Inq varchar(255),
                Bd varchar(255)
                );
            '''
        cur.execute(sql1)
        cur.execute(sql2)
        conn.commit()
        conn.close()

    def insertData(self):
        conn = pymysql.connect("localhost",self.user,self.password,self.database)
        cur = conn.cursor()
        for data in self.dataList:
            for index in range(len(data)):
                data[index] = '"'+data[index]+'"'
            sql = '''insert into moive_doubanTOP250(link,imgsrc,chinese_name,foreign_name,rating,judgeNum,Inq,Bd)
                values(%s)'''%",".join(data)
            cur.execute(sql)
        conn.commit()
        conn.close()


if __name__ == "__main__":
    main()
           

分析

这次采取使用python类的方式来写,也当作一起复习了,主要操作就是连接数据库,然后创建游标,游标执行写好的SQL语句,然后提交,关闭连接。

在SaveMySQL这个类里主要就是2个方法,一个创建表,一个插入数据,都比较简单,需要注意的是在写插入数据的SQL的时候,用join函数把每一个数据用逗号隔开,也就是这种写法

sql = '''insert into moive_doubanTOP250(link,imgsrc,chinese_name,foreign_name,rating,judgeNum,Inq,Bd)
                values(%s)'''%",".join(data)
           

非常方便,不愧是python。

保存结果:

Python爬虫 爬取豆瓣电影TOP250Python爬虫 爬取豆瓣电影TOP250

发现movie拼错了。。。有点无语

中间这一些问号是爬取网页提取的时候没有换掉的\x0,没有太大的问题,想换的话直接

或者用re里面的sub都可以解决。

总结

这次学习的爬虫知识比较简单,爬的网页也只是对html进行了一些分析,上手还是很快的,基本上就是对Python的库的调用,只能说python永远滴神,当然对于防止被ban也有一定的了解了,学到了网络中应用层的一些机制,这学期计网就是我永远的痛。。。

找个时间继续深入了解一下这个爬虫吧,希望还能写下一篇爬虫的博客。

对了,附上完整的代码:

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

'WebSpider Module'

__author__ = 'Handson Huang'

from bs4 import BeautifulSoup
import re # 正则表达式
import urllib.request,urllib.error
import xlwt
import pymysql
from typing import List

def main():
    print("开始爬取数据")
    baseUrl = "https://movie.douban.com/top250?start="
    dataList = getData(baseUrl)                      # 爬取数据
    print("爬取数据完成")
    print(len(dataList))                             # 判断是否爬取到内容
    savePath = r'spider.xls'
    saveData(dataList,savePath)                      #保存数据到excel中
    print("保存数据到excel表中完成")
    sm = SaveMySQL("用户名","密码","数据库名",dataList) #保存数据到MySQL中
    sm.createTable()
    sm.insertData()
    print("保存数据到MySQL中完成")

findLink = re.compile(r'<a href="(.*?)" target="_blank" rel="external nofollow"  target="_blank" rel="external nofollow" >')           # 提取影片链接的正则表达式
findImgSrc = re.compile(r'<img.*src="(.*?)"',re.S)   # 提取图片链接,re.S让换行符包含在字符中
findTitle = re.compile(r'<span class="title">(.*)</span>') # 提取影片名称
findRating = re.compile(r'<span class="rating_num" property="v:average">(.*)</span>') # 提取评分
findJudgeNum = re.compile(r'<span>(\d*)人评价</span>') # 评分人数
findInq = re.compile(r'<span class="inq">(.*)</span>') # 影片介绍
findBd = re.compile(r'<p class="">(.*?)</p>',re.S)    # 影片背景

# 爬取网页数据    
def getData(url):
    dataList = []
    for i in range(0,10):
        tmp_url = url + str(i*25)
        html = askURL(tmp_url)
        # 逐一解析网页
        soup = BeautifulSoup(html,"html.parser")
        for item in soup.find_all("div",class_="item"):
            item = str(item)
            data = []
            link = re.findall(findLink,item)[0] # 获取影片链接
            data.append(link)
            imgSrc = re.findall(findImgSrc,item)[0]
            data.append(imgSrc)
            title = re.findall(findTitle,item)
            if len(title) == 2:
                ctitle = title[0]
                data.append(ctitle)
                foreign_title = title[1].replace("/","")
                data.append(foreign_title)
            else:
                ctitle = title[0]
                foreign_title = " " # 没有外文名时留空
                data.append(ctitle)
                data.append(foreign_title)
            rating = re.findall(findRating,item)[0]
            data.append(rating)
            judgeNum = re.findall(findJudgeNum,item)[0]
            data.append(judgeNum)
            inq = re.findall(findInq,item)
            if len(inq) != 0:
                inq = inq[0].replace("。","")
                data.append(inq)
            else:
                inq = " "
                data.append(inq)
            bd = re.findall(findBd,item)[0]
            bd = re.sub(r'<br(\s+)?/>(\s+)?'," ",bd) # 去掉bd中的<br/>
            data.append(bd.strip()) # 去掉前后的空格
            dataList.append(data)

    return dataList

def askURL(url):
    head = {
            "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.105 Safari/537.36"
            }
    req = urllib.request.Request(url,headers=head)

    html = ""
    try:
        response = urllib.request.urlopen(req)
        html = response.read().decode("utf-8")
    except urllib.error.HTTPError as e:
        if hasattr(e,"code"):
            print(e.code)
        if hasattr(e,"reason"):
            print(e.reason)
    return html

#保存数据到excel表里
def saveData(dataList,savePath):
    book = xlwt.Workbook(encoding="utf-8")
    sheet = book.add_sheet('豆瓣电影top250',cell_overwrite_ok=True)
    col = ("影片链接","图片链接","影片中文名称","影片外文名","评分","评论人数","影片介绍","影片背景")
    for i in range(len(col)):
        sheet.write(0,i,col[i])
    for k in range(len(dataList)):
        data = dataList[k]
        for j in range(len(data)):
            sheet.write(k+1,j,data[j])
    book.save(savePath)

# 保存数据到mysql里
class SaveMySQL:
    def __init__(self, user:str, password:str, database:str, dataList:List):
        self.user = user
        self.password = password
        self.database = database
        self.dataList = dataList

    def createTable(self):
        conn = pymysql.connect("localhost",self.user,self.password,self.database)
        cur = conn.cursor()
        sql1 = "drop table if exists moive_doubanTOP250;"
        sql2 = '''
            create table moive_doubanTOP250(
                id int primary key auto_increment,
                link varchar(255),
                imgsrc varchar(255),
                chinese_name varchar(100),
                foreign_name varchar(100),
                rating float,
                judgeNum int,
                Inq varchar(255),
                Bd varchar(255)
                );
            '''
        cur.execute(sql1)
        cur.execute(sql2)
        conn.commit()
        conn.close()

    def insertData(self):
        conn = pymysql.connect("localhost",self.user,self.password,self.database)
        cur = conn.cursor()
        for data in self.dataList:
            for index in range(len(data)):
                data[index] = '"'+data[index]+'"'
            sql = '''insert into moive_doubanTOP250(link,imgsrc,chinese_name,foreign_name,rating,judgeNum,Inq,Bd)
                values(%s)'''%",".join(data)
            cur.execute(sql)
        conn.commit()
        conn.close()


if __name__ == "__main__":
    main()