python爬取论文数据_python爬虫系列之数据存储实战:爬取简书用户文章列表并保存...

前面讲了 json和 csv两个存储数据的库,在数据量比较少的时候,用这两个库很方便。

一、分析爬取逻辑

这一篇我们来爬取简书用户的文章列表,和之前爬取我的文章列表一样,我们要爬取的信息有:文章的标题

文章链接

访问量

评论数

点赞数

我们的 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页,换个人我们页得改代码,所以说这是假的自动化。

那怎么办呢?我们知道当爬到13页时应该没有文章了,那让我们看一下访问第14页会怎么样

可以看到第 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)

这样就好多了:

三、总结在敲代码之前要仔细分析

尽量写出模块化的代码,这样便于修改,代码的逻辑和结构页更加清晰

json库不能实时写入数据,只能在最后一起写入,对内存要求较大

csv库可以逐行写入也可以逐行读取,但是在操作时一定要注意数据的结构,任何一行出现缺漏都会造成很大影响

在进行数据读取的时候一定要注意编码,出错往往是编码的问题

觉得不错就点个赞吧(ˇ∀ˇ)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值