爬虫代码改进(二)|多页抓取与二级页面

3,068 阅读6分钟
原文链接: zhuanlan.zhihu.com

本文是下面两篇文章的续篇

爬虫基本原理

爬虫代码改进(一)

本系列包括如下内容

  • 抓取豆瓣top250一页多个字段
  • 整合成列表
  • 存储为json文件
  • 定义成函数形式
  • 多页抓取之构造url
  • 多页抓取之翻页
  • 抓取二级页面数据
  • 通过生成器优化代码
  • 改写为类的形式

本文主要讲

  • 多页抓取之构造url
  • 多页抓取之翻页
  • 抓取二级页面数据

上一篇文章我们定义函数,抓取豆瓣top250一页的数据,代码如下

import requests # 导入网页请求库
from bs4 import BeautifulSoup # 导入网页解析库
import json

def start_requests(url):
    r = requests.get(url)
    return r.content

def parse(text):
    soup = BeautifulSoup(text, 'html.parser')
    movie_list = soup.find_all('div', class_ = 'item')
    result_list = []
    for movie in movie_list:
        mydict = {}
        mydict['title'] = movie.find('span', class_ = 'title').text
        mydict['score'] = movie.find('span', class_ = 'rating_num').text
        mydict['quote'] = movie.find('span', class_ = 'inq').text
        star = movie.find('div', class_ = 'star')
        mydict['comment_num'] = star.find_all('span')[-1].text[:-3]
        result_list.append(mydict)
    return result_list

def write_json(result):
    s = json.dumps(result, indent = 4, ensure_ascii=False)
    with open('movies.json', 'w', encoding = 'utf-8') as f:
        f.write(s)

def main():
    url = 'https://movie.douban.com/top250'
    text = start_requests(url)
    result = parse(text)
    write_json(result)

if __name__ == '__main__':
    main()

接下来我们要根据这个代码进行改进。

多页抓取之构造url

上一篇文章我们已经完善了抓取一个页面的爬虫代码,现在我们需要抓取10个页面250个电影的信息,抓取多页信息一般有两种方式,一种是构造url,一种是翻页,本节我们来讲如何构造url。

我们可以直接看这几页的链接有什么规律

第一页  https://movie.douban.com/top250
第二页  https://movie.douban.com/top250?start=25&filter=
第三页  https://movie.douban.com/top250?start=50&filter=
第四页  https://movie.douban.com/top250?start=75&filter=

可以发现除了第一页,后面都只换了一个数字,而且是等差数列。那么我们可以猜想第一页这样可不可以

https://movie.douban.com/top250?start=0&filter=

将这个链接输入浏览器发现其实就是第一页,所以我们就可以根据这个规律构造url字符串了,抓取250个电影只需要一个循环。我们现在还是只抓取标题打印出来

import requests # 导入网页请求库
from bs4 import BeautifulSoup # 导入网页解析库

def start_requests(url):
    r = requests.get(url)
    return r.content

def parse(text):
    soup = BeautifulSoup(text, 'html.parser')
    movie_list = soup.find_all('div', class_ = 'item')
    for movie in movie_list:
        print(movie.find('span', class_ = 'title').text)

def main():
    for i in range(10):
        url = 'https://movie.douban.com/top250?start={}&filter='.format(i * 25)
        text = start_requests(url)
        parse(text)

if __name__ == '__main__':
    main()

接下来,我们需要抓取多个字段,存储到json文件中,这时,我们就要把多页的电影信息放在一个list里,再保存为文件。(注意代码中的注释)

import requests # 导入网页请求库
from bs4 import BeautifulSoup # 导入网页解析库
import json

def start_requests(url):
    r = requests.get(url)
    return r.content

def parse(text):
    soup = BeautifulSoup(text, 'html.parser')
    movie_list = soup.find_all('div', class_ = 'item')
    for movie in movie_list:
        mydict = {}
        mydict['title'] = movie.find('span', class_ = 'title').text
        mydict['score'] = movie.find('span', class_ = 'rating_num').text
        quote = movie.find('span', class_ = 'inq')
        mydict['quote'] = quote.text if quote else None # 抓取10页就总会遇到这种特殊情况要处理
        star = movie.find('div', class_ = 'star')
        mydict['comment_num'] = star.find_all('span')[-1].text[:-3]
        result_list.append(mydict) # 向全局变量result_list中加入元素

def write_json(result):
    s = json.dumps(result, indent = 4, ensure_ascii=False)
    with open('movies.json', 'w', encoding = 'utf-8') as f:
        f.write(s)

def main():
    for i in range(10):
        url = 'https://movie.douban.com/top250?start={}&filter='.format(i * 25)
        text = start_requests(url)
        parse(text)
    write_json(result_list) # 所有电影都存进去之后一起输出到文件

if __name__ == '__main__':
    # 初始化,注意不要在main()函数里定义,因为那里不是全局变量,其他函数无法调用
    result_list = []
    main()

多页抓取之翻页

翻页原理是爬取一页的信息的同时,把下一页的url也爬取到,再对抓取到的这个url进行爬取。这种方法适用于有“下一页”标签的网站,而且一般是网页url无法构造的时候才用这种方法。

用这种方法要注意对有无下一页进行判断

import requests # 导入网页请求库
from bs4 import BeautifulSoup # 导入网页解析库
import json

def start_requests(url):
    r = requests.get(url)
    return r.content

def parse(text):
    soup = BeautifulSoup(text, 'html.parser')
    movie_list = soup.find_all('div', class_ = 'item')
    for movie in movie_list:
        mydict = {}
        mydict['title'] = movie.find('span', class_ = 'title').text
        mydict['score'] = movie.find('span', class_ = 'rating_num').text
        quote = movie.find('span', class_ = 'inq')
        mydict['quote'] = quote.text if quote else None # 抓取10页就总会遇到这种特殊情况要处理
        star = movie.find('div', class_ = 'star')
        mydict['comment_num'] = star.find_all('span')[-1].text[:-3]
        result_list.append(mydict) # 向全局变量result_list中加入元素
    nextpage = soup.find('span', class_ = 'next').a # 找到“下一页”位置
    if nextpage:# 找到的就再解析,没找到说明是最后一页,递归函数parse就运行结束 
        nexturl = baseurl + nextpage['href']
        text = start_requests(nexturl) # 多次使用这个函数,可以看出定义函数的好处,当请求更复杂的时候好处更明显 
        parse(text)

def write_json(result):
    s = json.dumps(result, indent = 4, ensure_ascii=False)
    with open('movies.json', 'w', encoding = 'utf-8') as f:
        f.write(s)

def main():
    text = start_requests(baseurl)
    parse(text)
    write_json(result_list) # 所有电影都存进去之后一起输出到文件

if __name__ == '__main__':
    baseurl = 'https://movie.douban.com/top250'
    result_list = []
    main()

抓取二级页面数据

我们通常称这种列表形式的页面为一级页面,而这种每个对象单独的页面为二级页面。前面文章我们只是从一级页面中提取信息,但是一级页面展示的信息毕竟有限,因此我们有时候需要进入每个对象自己的页面去抓取更多信息。

下面我们抓取2页(50)个电影的详情页,因为2页和10页代码上没什么差异,而10页的话就要访问网站两百多次,可能爬虫会被封掉,这个问题不属于本文的研究范围,因此我们先避过,只爬两页。

我们抓取标题、上映时间、电影时长三个指标。

代码逻辑是,抓取每个电影的链接,再对每个链接进行请求,解析每个电影详情页获取数据

代码如下

import requests # 导入网页请求库
from bs4 import BeautifulSoup # 导入网页解析库
import json

# 发起请求
def start_requests(url):
    print(url) # 用这条命令知道当前在抓取哪个链接,如果发生错误便于调试
    r = requests.get(url)
    return r.content

# 解析一级网页,获取url列表
def get_page(text):
    soup = BeautifulSoup(text, 'html.parser')
    movies = soup.find_all('div', class_ = 'info')
    pages = []
    for movie in movies:
        url = movie.find('div', class_ = 'hd').a['href']
        pages.append(url)
    return pages

# 解析二级网页,获取信息
def parse_page(text):
    soup = BeautifulSoup(text, 'html.parser')
    mydict = {}
    mydict['title'] = soup.find('span', property = 'v:itemreviewed').text
    mydict['duration'] = soup.find('span', property = 'v:runtime').text
    mydict['time'] = soup.find('span', property = 'v:initialReleaseDate').text
    return mydict

# 将数据读取到json文件中
def write_json(result):
    s = json.dumps(result, indent = 4, ensure_ascii=False)
    with open('movies.json', 'w', encoding = 'utf-8') as f:
        f.write(s)

def main():
    for i in range(7, 9):
        url = 'https://movie.douban.com/top250?start={}&filter='.format(i * 25)
        text = start_requests(url)
        pageurls = get_page(text) # 解析一级页面
        for pageurl in pageurls: # 解析二级页面 
            page = start_requests(pageurl)
            mydict = parse_page(page)
            result_list.append(mydict)
    write_json(result_list) # 所有电影都存进去之后一起输出到文件

if __name__ == '__main__':
    result_list = []
    main()

上面代码经常定义一些全局变量,感觉代码设计上会有一些不优雅,下一篇文章通过定义生成器和类来解决这种问题

专栏信息

专栏主页:python编程

专栏目录:目录

爬虫目录:爬虫系列目录

版本说明:软件及包版本说明