json和csv库的使用
Posted 123Wheels
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了json和csv库的使用相关的知识,希望对你有一定的参考价值。
前面讲了 json和 csv两个存储数据的库,在数据量比较少的时候,用这两个库很方便。
一、分析爬取逻辑
这一篇我们来爬取简书用户的文章列表,和之前爬取我的文章列表一样,我们要爬取的信息有:
文章的标题
文章链接
访问量
评论数
点赞数
网页分析请看:python爬虫系列之 html页面解析:如何写 xpath路径
我们的 xpath如下:
#获取所有 li标签
xpath_items = '//ul[@class="note-list"]/li'
#对每个 li标签再提取
xpath_link = './div/a/@href'
xpath_title = './div/a/text()'
xpath_comment_num = './/div[@class="meta"]/a[2]/text()'
xpath_heart_num = './/div[@class="meta"]/span/text()'
我们的爬取目标是列表里的一位文章数较多的:Python测试开发人工智能
他写了111篇文章,累计24万余字。
我们今天的目标就是爬取他所有文章的标题、链接、访问量、评论数和点赞数。
分析完成了,就到了爬虫时间。
大家刚一看可能会觉得很简单,但是当开始爬时就会发现问题并不简单。
在前面爬我的文章列表的例子里,一次请求就可以获得我的全部文章了,但那是因为我的文章还比较少,所以一次请求就全部获取到。
实际上简书在这里使用了懒加载,当你向下滚动页面时会自动加载下一页,每次加载9篇文章,所以在上次的例子中一个请求就获取到了我全部的文章。
那怎么办呢?别担心,经过一番抓包,终于找到了懒加载的链接,大家可以直接拿去用。
至于抓包是什么,怎么抓包就留到以后讲。链接如下:
url = 'https://www.jianshu.com/u/3313b20a4e25?order_by=shared_at&page=1'
#其中order_by是排序方式,这个不用管
#page是当前页数
#3313b20a4e25是一个类似用户 id的字符串,每个账号都不同
#可以从主页链接中提取出来 如 https://www.jianshu.com/u/9bc194fde100
https://www.jianshu.com/u/3313b20a4e25?order_by=shared_at&page=
链接返回的是一个 html代码片段,和页面上的文章列表那一段相同,我们可以直接应用 xpath。
另外,一个爬虫应该是自动化的,也就是说至少得要能够在爬取完毕后自动停止,所以我们的第一个问题就是:
question-1:如何判断数据爬取完毕了
这里我们仔细一想,这个账号下有111篇文章,那么最多只有111 / 9 + 1 = 13页,那我们的代码可以这样写:
base_url = 'https://www.jianshu.com/u/9bc194fde100?order_by=shared_at&page='
for i in range(12):
url = base_url + str(i + 1)
... ...
这样写很不好,虽然爬虫可以自动停止了,但是过几个月再来爬说不定就有150篇了,这时候我们就得改代码。
而且不可能每个人的文章都刚好是13页,换个人我们页得改代码,所以说这是假的自动化。
那怎么办呢?我们知道当爬到14页时应该没有文章了,那让我们看一下访问第13页会怎么样
可以看到第 14页是动态页面,这里不得不吐槽一下简书,竟然多个接口混用,不应该是 404 not found吗。这样平白给我们的爬取增添了一些麻烦。
不过还好已经知道问题是什么了,这样就只要想出解决办法就好。
观察一下发现当我们在文章栏目下,也就是页数小于 14的时候,文章的标签是激活的,而当我们在动态的栏目下时,动态的标签是激活的(动态两个字下有一个横杠,表示处于激活状态)。
显然在这两个之间同时只能有一个处于激活状态,所以我们可以通过查看文章标签的状态来判断是否爬取完成。
但是... ....
我们又发现在用户的名字下面就有用户的文章数,我们可以获取用户的文章数再计算出总页面数啊!!!(简直被自己蠢哭(;´д`)ゞ)
二、代码实现
分析结束,下面看代码部分:
我们先定义一个生成器,接受简书用户的唯一标识符,先获取用户当前的文章数,然后通过文章数计算出页面数,再根据页面数来生成对应用户的文章列表的链接:
#url生成器
def urlsGenerater(uid):
# 设置请求头
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.117 Safari/537.36'
}
r = requests.get('https://www.jianshu.com/u/{}?order_by=shared_at&page={}'.format(uid, 1), headers=headers)
dom = etree.HTML(r.text)
#获取文章数量和最大页数
article_num = int(dom.xpath('//div[@class="info"]//li[3]//p/text()')[0].strip())
print(article_num)
max_page_num = article_num / 9
i = 1
while True:
yield 'https://www.jianshu.com/u/{}?order_by=shared_at&page={}'.format(uid, i)
if i >= max_page_num:
break
i+=1
定义一个函数 getArticleItems,接受用户文章列表的链接,返回文章列表的对象数组:
#获取文章的 xpath数组
def getArticleItems(url):
#设置请求头
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.117 Safari/537.36'
}
# 获取所有 li标签
xpath_items = '//ul[@class="note-list"]/li'
r = requests.get(url, headers=headers)
dom = etree.HTML(r.text)
return dom.xpath(xpath_items)
定义一个函数 getDetails,接受一个文章的 xpath对象,以字典格式返回文章的相关信息:
#获取文章的相关信息
def getDetails(article_item):
# 对每个 li标签再提取
details_xpath = {
'link': './div/a/@href',
'title': './div/a/text()',
'comment_num': './/div[@class="meta"]/a[2]/text()',
'heart_num': './/div[@class="meta"]/span/text()',
}
items = details_xpath.items()
detail = {}
for key, path in items:
detail[key] = ''.join(article_item.xpath(path)).strip()
return detail
将上面的几个模块组合起来,先把获取到的数据打印出来看是否符合要求:
uid = '9bc194fde100'
urls = urlsGenerater(uid)
for url in urls:
article_items = getArticleItems(url)
for article_item in article_items:
print(getDetails(article_item))
打印结果:
可以看到,爬取的信息已经基本符合我们的要求了,下面就剩如何把信息保存下来了。
我们用 json和 csv两个库来保存数据。
根据模块化的编程思想,我们先写两个函数 csvSaveMethod和 jsonSaveMethod
#通过 csv来保存数据 这里 csvobj要求是 csv.DictWriter
def csvSaveMethod(csvobj, data):
csvobj.writerow(data)
#通过 json来保存数据 这里的 data必须是所有结果组成的一个列表
def jsonSaveMethod(fileobj, data):
json.dump(data, fileobj)
下面是使用 csvSaveMethod和 jsonSaveMethod的代码:
uid = '9bc194fde100'
urls = urlsGenerater(uid)
#保存 json结果的容器
results = []
#用 csvSaceMethod
with open('data.csv', 'w', newline='', encoding='utf-8') as csvfile:
fieldnames = ['link', 'title', 'comment_num', 'heart_num']
csvobj = csv.DictWriter(csvfile, fieldnames=fieldnames)
csvobj.writeheader()
for url in urls:
article_items = getArticleItems(url)
for article_item in article_items:
details = getDetails(article_item)
#将结果添加到 results中,等下用 json写入
results.append(details)
csvSaveMethod(csvobj, details)
#用 jsonSaveMethod
with open('data.json', 'w', encoding='utf-8') as fp:
jsonSaveMethod(results, fp)
结果截图:
我们发现 jsonSaveMethod方法产生的 json文件里的内容没有排版,而且中文全部转化成 ascii编码了,这样不便于查阅。
为了解决这个问题,我们对 jsonSaveMethod做一些改动:
def jsonSaveMethod(data, fileobj):
json.dump(data, fileobj, ensure_ascii=False, indent=2)
这样就好多了:
完整的代码请访问 github:https://github.com/geebos/pythoncrawler/blob/master/projectjsonandcsv/crawljanshuarticles_info.py
三、总结
在敲代码之前要仔细分析
尽量写出模块化的代码,这样便于修改,代码的逻辑和结构页更加清晰
json库不能实时写入数据,只能在最后一起写入,对内存要求较大
csv库可以逐行写入也可以逐行读取,但是在操作时一定要注意数据的结构,任何一行出现缺漏都会造成很大影响
在进行数据读取的时候一定要注意编码,出错往往是编码的问题
以上是关于json和csv库的使用的主要内容,如果未能解决你的问题,请参考以下文章
错误代码:错误域 = NSCocoaErrorDomain 代码 = 3840“JSON 文本没有以数组或对象和允许未设置片段的选项开头。”