爬虫代码改进(一)|多字段整合存储

395 阅读5分钟
原文链接: zhuanlan.zhihu.com

上一篇文章爬虫基本原理中,我们展示了单页爬虫抓取,将结果打印出来的过程。接下来,我们要对基本代码进行改进,使其适应大多数实战场景

  • 实现多个字段、多页、二级页面抓取
  • 将抓取到的内容存储到文件
  • 定义函数、类,使用生成器来优化代码

这个系列以豆瓣top250网站为例,主要分为如下几个部分

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

本文包括前四个部分,如何从网页中定位信息已经在上一篇文章中讲到,这里就不再赘述

抓取豆瓣top250一页多个字段

我们就抓一页上的标题、评分、评价人数和名言吧。

我们一步一步来,先打印标题

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

r = requests.get('https://movie.douban.com/top250')
soup = BeautifulSoup(r.content, 'html.parser')
movie_list = soup.find_all('div', class_ = 'item')

for movie in movie_list:
    title = movie.find('span', class_ = 'title').text
    print(title)

可以看到打印出了25个电影标题

像这种只抓取一个字段的,很多时候可以直接find_all定位到span title上,而不用先构造list,再循环list

下一步,加入其他字段(字段多的时候就一定要先存movie_list再对每一个分别提取)

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

r = requests.get('https://movie.douban.com/top250')
soup = BeautifulSoup(r.content, 'html.parser')
movie_list = soup.find_all('div', class_ = 'item')

for movie in movie_list:
    title = movie.find('span', class_ = 'title').text
    score = movie.find('span', class_ = 'rating_num').text
    quote = movie.find('span', class_ = 'inq').text
    star = movie.find('div', class_ = 'star')
    comment_num = star.find_all('span')[-1].text[:-3]
    print(title, score, '\n',comment_num, quote, '\n')

得到结果如下

代码都类似,只是找评论人数的时候会涉及到从list中提取,字符串处理之类的事情,不过这属于python基础知识,放在这里只是提醒没有十全十美的解析库,很多时候还是需要字符串处理知识的。

整合成列表

上面我们是将抓取到的信息直接print出来,而真正抓取数据的时候肯定要把数据存起来,这就需要把这些数据组织在一起以方便存储,比如这里我们存储为以字典为元素的列表

import requests # 导入网页请求库
from bs4 import BeautifulSoup # 导入网页解析库
import pprint # 使打印出来的列表更方便看

r = requests.get('https://movie.douban.com/top250')
soup = BeautifulSoup(r.content, '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) # 每抓取一条都加到result_list中

# 显示结果
pp = pprint.PrettyPrinter(indent=4)
pp.pprint(result_list)

输出结果如下

这虽然也是打印,但是是把结果组织成一个list再一起打印出来的,在存储成文件或者进一步处理都有非常大的好处。

存储为json文件

json文件的介绍引用百度百科上的解释

JSON(JavaScript Object Notation, JS 对象标记) 是一种轻量级的数据交换格式。它基于 ECMAScript (w3c制定的js规范)的一个子集,采用完全独立于编程语言的文本格式来存储和表示数据。简洁和清晰的层次结构使得 JSON 成为理想的数据交换语言。 易于人阅读和编写,同时也易于机器解析和生成,并有效地提升网络传输效率。

json格式的文件在爬虫中使用非常广泛,json存储的文本是按照类似python中的列表和字典的形式组织的,一个json文件的样子是类似这样的

可以看到上面json中的内容其实就是以字典为元素的一个list,所以将数据存储成json格式只需要传入python这样的对象即可,非常方便。

将抓取到的数据存储到json文件中,代码如下

import requests # 导入网页请求库
from bs4 import BeautifulSoup # 导入网页解析库
import json # 用于将列表字典(json格式)转化为相同形式字符串,以便存入文件

r = requests.get('https://movie.douban.com/top250')
soup = BeautifulSoup(r.content, '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)

# 将result_list这个json格式的python对象转化为字符串
s = json.dumps(result_list, indent = 4, ensure_ascii=False)
# 将字符串写入文件
with open('movies.json', 'w', encoding = 'utf-8') as f:
    f.write(s)

打开movies.json文件是这样的

上面json.dumps先当做存储到json文件的固定用法即可,之后的文章会讲json的更细节部分。

如果要读取这个json文件的话只需要这样

import json
with open('movies.json', encoding = 'utf-8') as f:
    s = f.read()
data = json.loads(s)
print(data)

这个data变量就和刚才的result_list完全一样了。如果要处理刚才抓取的数据,可以使用pandas库将其转化为数据框

import json
import pandas as pd
with open('movies.json', encoding = 'utf-8') as f:
    s = f.read()
data = json.loads(s)
df = pd.DataFrame(data)
print(df)

结果如下

当然,如果你抓取到数据想直接处理,也可以对result_list直接转化为dataframe。

我们从这个例子里就可以看到把结果组织起来相比于直接打印出来的好处。

定义成函数的形式

写爬虫一定要习惯封装函数,在爬虫比较复杂的时候才能展现更清晰的逻辑,我们现在就来练习一下

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

# 将数据写入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():
    url = 'https://movie.douban.com/top250'
    text = start_requests(url)
    result = parse(text)
    write_json(result)

# 一般做法
if __name__ == '__main__':
    main()

定义成函数没什么可说的,养成习惯就好。之后抓取更多页面就可以看出定义函数的好处。

上面代码中`if __name__ == '__main__':`是一种惯用法,具体是用来干什么的看这篇文章

专栏信息

专栏主页:python编程

专栏目录:目录

爬虫目录:爬虫系列目录

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