本文是下面两篇文章的续篇
爬虫基本原理 https://zhuanlan.zhihu.com/p/35324806
爬虫代码改进(一) https://zhuanlan.zhihu.com/p/35325679
本系列包括如下内容
抓取豆瓣 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()
抓取二级页面数据
我们通常称这种 https://movie.douban.com/top250 列表形式的页面为一级页面, 而这种 https://movie.douban.com/subject/1292052/ 每个对象单独的页面为二级页面. 前面文章我们只是从一级页面中提取信息, 但是一级页面展示的信息毕竟有限, 因此我们有时候需要进入每个对象自己的页面去抓取更多信息.
下面我们抓取 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 编程 https://zhuanlan.zhihu.com/python-programming
专栏目录: 目录 https://zhuanlan.zhihu.com/p/33896532?refer=python-programming
爬虫目录: 爬虫系列目录 https://zhuanlan.zhihu.com/p/35379237
版本说明: 软件及包版本说明 https://zhuanlan.zhihu.com/p/29663336
来源: https://juejin.im/entry/5acdf44f518825557e78c08d