使用爬虫爬取页面步骤:

  • step1:使用urllib库或者request库得到页面,一般用request,比较方便
  • step2:我们request得到的对象进行解码处理,因为在网页和我们电脑上进行传输和存储的数据一般为bytes类型,而在pytho中对其操作则需要字符串类型
  • step3:在我们要爬取的网页端检查元素,分析网页结构
  • step4:使用xpath或者bs4或者正则表达式对页面进行解析(这里以xpath举例,因为后面的我还没学^_^)
  • step5:将解析的结果保存就ok了

豆瓣电影

如果我们需要获得即将上市的电影的信息:

首先使用request库请求页面,然后对得到的响应进行解码处理:

headers = {
    'User-Agent' : 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.89 Safari/537.36',
    'Referer' : 'https://movie.douban.com/' #如果有的话,一定要加上,反爬虫机制
}

url = 'https://movie.douban.com/cinema/nowplaying/wuhan/'

response = requests.get(url, headers = headers)
response = response.text   #类型要变成text(string类型),或者content.decode('你需要的编码格式'),否则etree.HTML会出错

然后分析页面,可以发现,我们所需要的信息,都放在一个无序列表中,无序列表有一个属性:class = 'lists',我们先根据这个来定位。

result = etree.HTML(response)  #使用etree下的HTML方法构造一个htmlElement类型的元素

uls = result.xpath("//ul[@class = 'lists']")[0]   #获取第一个ul中class属性值为lists的对象
lis = uls.xpath("./li")                #获取其下的所有li标签,因为我们所需要的信息都保存在li标签中

现在,即将上映的电影的信息,都被保存在了lis这个列表中,我们遍历列表,然后以字典的形式存储信息

在属性中,比较特殊的是海报的封面,它并不是直接出现在li标签中,而是其下面的标签,如图:

for li in lis:
    title = li.xpath("@data-title")[0]    #获取li标签中,元素为data-title的西信息
    duration = li.xpath("@data-duration")[0]
    region = li.xpath("@data-region")[0]
    director = li.xpath("@data-director")[0]
    actors = li.xpath("@data-actors")[0]
    pic = li.xpath(".//img/@src")[0]  #由于图片比较特殊,所以与前面的不太一样
    movie = {
        "title" : title,
        "duration" : duration,
        "region" : region,
        "director" : director,
        "actors" : actors,
        "pic" : pic
    }
    movies.append(movie)  #保存到字典中

for movie in movies :
    print(movie)

这就是爬取豆瓣电影的全部过程了,相对来说是非常简单的

全部代码:

import requests
from lxml import etree

headers = {
    'User-Agent' : 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.89 Safari/537.36',
    'Referer' : 'https://movie.douban.com/' #如果有的话,一定要加上,反爬虫机制
}

url = 'https://movie.douban.com/cinema/nowplaying/wuhan/'

response = requests.get(url, headers = headers)
response = response.text   #类型要变成text(string类型),否则etree.HTML会出错

result = etree.HTML(response) #使用etree下的HTML方法构造一个htmlElement类型的元素

uls = result.xpath("//ul[@class = 'lists']")[0]   #获取第一个ul中class属性值为lists的对象
lis = uls.xpath("./li")                #获取其下的所有li标签

movies = []

for li in lis:
    title = li.xpath("@data-title")[0]             #分别获取对象
    duration = li.xpath("@data-duration")[0]
    region = li.xpath("@data-region")[0]
    director = li.xpath("@data-director")[0]
    actors = li.xpath("@data-actors")[0]
    pic = li.xpath(".//img/@src")[0]
    movie = {
        "title" : title,
        "duration" : duration,
        "region" : region,
        "director" : director,
        "actors" : actors,
        "pic" : pic
    }
    movies.append(movie)  #保存到字典中

for movie in movies :
    print(movie)

执行结果:

电影天堂

如果我们要获取电影天堂前七页中的电影的详细信息

红框中表示我们需要获取的信息:

url获取

首先考虑获取每一页电影的url,多点几个页面,发现有如下规律,当我们在第二页时,域名显示如下:

在第三页时,域名为:https://www.dytt8.net/html/gndy/dyzz/list_23_3.html
在第四页时,域名为:https://www.dytt8.net/html/gndy/dyzz/list_23_4.html
...
再从其他页面跳转到第一页时,域名为https://www.dytt8.net/html/gndy/dyzz/list_23_1.html

那么规律就很明显了,所以我们第一步应该是先要获取到每一中,电影的url,检查页面可知,a标签被放在一个table标签中,class为tbspan,所以可以根据这个来选择。

for index in range(1, 8):
        url = 'https://www.dytt8.net/html/gndy/dyzz/list_23_' + str(index) +'.html' #页面的url
        detail_urls = get_detail_urls(url) #获取当前页面中,所有电影的url

def get_detail_urls(url):
    response = requests.get(url, headers = headers, verify = False)
    text = response.content.decode('gbk', 'ignore')  #由于电影天堂这个网站并不是很规范,用response.text的时候,会出现乱码,所以这里用content然后自己解码

    # 构造htmlElement对象
    html = etree.HTML(text)
    detail_urls = html.xpath("//table[@class = 'tbspan']//a/@href")  #获取url

    #加上主域名
    for index in range(len(detail_urls)):   #看上图可知,获取到的url都只有后半截,所以我们需要手动加上前半截 BASE = 'https://www.dytt8.net'
        detail_urls[index] = BASE + detail_urls[index]
    return detail_urls

详情页获取

url获取完成之后,我们就需要根据这些url获取每一个电影详情页里面的东西了,包括电影名、海报封面、译名、片名、产地、简介、下载地址这些东西。

def start():
    movies = []
    for index in range(1, 8):
        url = 'https://www.dytt8.net/html/gndy/dyzz/list_23_' + str(index) +'.html'
        detail_urls = get_detail_urls(url)  #当前页面的所有url拿到手
        for detail_url in detail_urls:  #对于每个url,也就是每一部电影
            movie = parse_detail_page(detail_url)  #解析详情页面,并返回解析的结果,这个函数后面会实现
            movies.append(movie)   #将电影信息保存在列表中
            print(movie)

详情页解析:

首先看看详情页的信息,可以发现是非常的.......真的一点都不规范,所有的信息都被包裹在一个p标签中,所以获取信息非常麻烦

首先获取标题,根据class和font color来获取:

def parse_detail_page(url):
    movie = {}
    requests.packages.urllib3.disable_warnings()
    response = requests.get(url, headers=headers, verify=False)
    text = response.content.decode('gbk', 'ignore')
    html = etree.HTML(text)
    title = html.xpath("//div[@class = 'title_all']//font[@color = '#07519a']/text()")[0]  #获取标题
    movie['title'] = title
    return movie

然后获取图片,根据Zoom和img来获取,图片这里要说一下,因为可能有的页面有两张图片:一个是海报封面post,另一个是电影截图screenshot,也有的页面一张post,没有screenshot,也有的都没有,所以我们获取了图片信息之后要先判断一下

 ZoomElement = html.xpath("//div[@id='Zoom']")[0]
photo = ZoomElement.xpath(".//img/@src")
if (len(photo) > 0):   #如果photo这个列表中包含了图片,那就说明有海报:
    movie['cover'] = photo[0]
else:
    movie['cover'] = ""

if (len(photo) > 1): #如果photo包含了两张或两张以上的图片,则说明有截图
    movie['screenshot'] = photo[1]
else:
    movie['screenshot'] = ""

然后获取下方的文字信息:

    infos = ZoomElement.xpath(".//text()")   #看下方的注解1

    def parse_info(info, rule):
        return info.replace(rule, "").strip()

    for index, info in enumerate(infos):  #index为下标,info为内容
        if info.startswith("◎年  代"):        #如果是以年代开头
            info = parse_info(info, "◎年  代") #那么将字符串中“◎年  代”这个字符使用replace函数替换为空格,然后使用strip函数去除前后空格
            movie['year'] = info   #最终得到的就是一个年份数字,没有乱七八糟的空格字符啥的
        elif info.startswith("◎产  地"):
            info = parse_info(info, "◎产  地")
            movie['place'] = info
        elif info.startswith("◎类  别"):
            info = parse_info(info, "◎类  别")
            movie['category'] = info
        elif info.startswith("◎豆瓣评分"):
            info = parse_info(info, "◎豆瓣评分")
            movie['douban_rating'] = info
        elif info.startswith("◎片  长"):
            info = parse_info(info, "◎片  长")
            movie['duration'] = info
        elif info.startswith("◎导  演"):
            info = parse_info(info, "◎导  演")
        elif info.startswith("◎主  演"):     #到主演这里需要注意,因为可能一部电影中有很多主演,看注解2           
            info = parse_info(info, "◎主  演")  #首先处理第一行的主演,将其加入actor列表中
            actors = [info]
            for x in range(index + 1, len(infos)):  #然后往后循环,只要不到◎这个符号,那就说明是主演,继续提取
                actor = infos[x].strip()
                if (actor.startswith("◎")):
                    break
                actors.append(actor)
            movie['actors'] = actors
        elif info.startswith("◎简  介"):
            info = parse_info(info, "◎◎简  介")
            profile = [info]
            for x in range(index + 1, len(infos)):
                profile = infos[x].strip()
                movie['profile'] = profile
    download_url = html.xpath("//td[@bgcolor = '#fdfddf']/a/@href") #获取download链接
    if len(download_url) > 0:   #因为有的页面可能没有下载链接,所以判断一下
        movie['download_url'] = download_url[0]
    else:
        movie['downloada_url'] = ""

注解1:使用ZoomElement下,使用text得到问题,会返回一个列表,列表中的一个元素,就是下图中的一行:

注解2:可能一部电影中有很多个主演,我们需要将所有的主演都保存下来

全部的代码

import requests
from lxml import etree

BASE = 'https://www.dytt8.net'

headers = {
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.89 Safari/537.36'
}

def get_detail_urls(url):
    response = requests.get(url, headers=headers, verify=False)
    text = response.content.decode('gbk', 'ignore')

    # 构造htmlElement对象
    html = etree.HTML(text)
    detail_urls = html.xpath("//table[@class = 'tbspan']//a/@href")

    # 加上主域名
    for index in range(len(detail_urls)):
        detail_urls[index] = BASE + detail_urls[index]

    return detail_urls

def parse_detail_page(url):
    movie = {}
    response = requests.get(url, headers=headers, verify=False)
    text = response.content.decode('gbk', 'ignore')
    html = etree.HTML(text)
    title = html.xpath("//div[@class = 'title_all']//font[@color = '#07519a']/text()")[0]
    movie['title'] = title

    ZoomElement = html.xpath("//div[@id='Zoom']")[0]
    photo = ZoomElement.xpath(".//img/@src")
    if len(photo) > 0:
        movie['cover'] = photo[0]
    else:
        movie['cover'] = ""

    if len(photo) > 1:
        movie['screenshot'] = photo[1]
    else:
        movie['screenshot'] = ""

    infos = ZoomElement.xpath(".//text()")

    def parse_info(info, rule):
        return info.replace(rule, "").strip()

    for index, info in enumerate(infos):          #index为下标,info为内容
        if info.startswith("◎年  代"):          #如果是以年代开头
            info = parse_info(info, "◎年  代")  #那么将字符串中“◎年  代”这个字符使用replace函数替换为空格,然后使用strip函数去除前后空格
            movie['year'] = info                  #最终得到的就是一个年份数字,没有乱七八糟的空格字符啥的
        elif info.startswith("◎产  地"):
            info = parse_info(info, "◎产  地")
            movie['place'] = info
        elif info.startswith("◎类  别"):
            info = parse_info(info, "◎类  别")
            movie['category'] = info
        elif info.startswith("◎豆瓣评分"):
            info = parse_info(info, "◎豆瓣评分")
            movie['douban_rating'] = info
        elif info.startswith("◎片  长"):
            info = parse_info(info, "◎片  长")
            movie['duration'] = info
        elif info.startswith("◎导  演"):
            info = parse_info(info, "◎导  演")
        elif info.startswith("◎主  演"):
            info = parse_info(info, "◎主  演")
            actors = [info]
            for x in range(index + 1, len(infos)):
                actor = infos[x].strip()
                if (actor.startswith("◎")):
                    break
                actors.append(actor)
            movie['actors'] = actors
        elif info.startswith("◎简  介"):
            info = parse_info(info, "◎简  介")
            profile = [info]
            for x in range(index + 1, len(infos)):
                profile = infos[x].strip()
                if profile.startswith('【下载地址】') :
                    break
                movie['profile'] = profile
    download_url = html.xpath("//td[@bgcolor = '#fdfddf']/a/@href")
    if len(download_url) > 0:
        movie['download_url'] = download_url[0]
    else:
        movie['downloada_url'] = ""
    return movie

def start():
    movies = []
    for index in range(1, 8):
        url = 'https://www.dytt8.net/html/gndy/dyzz/list_23_' + str(index) + '.html'
        detail_urls = get_detail_urls(url)
        for detail_url in detail_urls:
            movie = parse_detail_page(detail_url)
            movies.append(movie)
            print(movie)

if __name__ == '__main__':
    start()

运行结果:

由于request版本的原因,所以有些版本可能会报ssl证书错误(我在pycharm上可以跑,但是jupyter上面就不行),这个我找了好久也没找到解决方案,如果有大佬了解的话,可以告诉我一下^_^


立志成为一名攻城狮