天天看点

python爬取ul下的li是空的_python爬取豆瓣首页热门栏目详细流程

记录一下爬取豆瓣热门专栏的经过,通过这篇文章,你能学会requests,HTMLParser,json的基本使用,以及爬取网页内容的基本思路。

使用模块

1,获取豆瓣首页代码:首先我们需要访问豆瓣页面,获取首页的源码。这里推荐使用第三方库:requests,相比python内置的 urllib 模块,requests使用起来更简单,功能更全面

2,对获取的代码进行解析:对于解析html代码,已经有很多功能强大的框架能使用,如Scrapy,PySpider,Beautiful Soup等,这里我们只是学习下爬虫的基本使用,所以内建的 HTMLParser 足够使用了

3,对获取的数据进行处理: json

思路分析

既然我们需要的只是热门专栏模块的数据,那么我们需要一个标志来告诉我们:下面的代码就是专栏模块了,准备获取数据。同样我们需要知道当前

读取的是图片、标题还是栏目类别,以此将数据储存到相应的字段中。总的来说,我们最起码应该通过代码来实现以下几点:

1,获取网页源码

2,通过自定义方法解析html

3,通过标志位判断当前数据是否是我们需要的数据

4,通过分析代码结构决定将要储存的数据结构

5,将数据按照特定格式进行本地储存

豆瓣官网:https://www.douban.com/,分析一下我们需要爬取模块的代码:

python爬取ul下的li是空的_python爬取豆瓣首页热门栏目详细流程

可以看到,我们需要爬取的数据都在 ul.time-list 这个代码块里,那么我们的标志位就是:当开始标签为 ul并且具有类名 time-list时,我们就要获取数据了,当结束标签为 ul 时,停止解析,继续分析代码结构,每个 li 里面包含了对应数据里面的 详情页跳转链接,图片地址,标题以及专栏类别,那么我们的数据结构到这里也就很清楚了:一个 li 块对应一条数据,每条数据由四个字段组成:

详情页跳转链接 href --> 这里我们考虑了一下, 还是通过第二个a标签来获取,它具有统一的类名title,同时我们还能获取 标题title,

图片地址 imgUrl --> 通过每个li代码块里面唯一img标签的src属性可以轻松获取,

标题 title --> 通过 a.title获取,

专栏类别 type --> 唯一的 span 标签获取

tip:像上面我们选取数据的标志位一样,img的alt可以获取标题,a标签的文本也可以获取标题,两个a标签都能获取跳转链接不管是爬虫还是平时其他的开发,我们经常会遇到,同一个需求有多种方法实现,这时候我们就需要思考一下哪一种方法更简洁,冷静分析后的编码不一定最优秀,但自己肯定印象深刻(说远了,回归正题)。

编码实现

通过上面的准备工作,我们已经确定了需要引入的模块,解析事件触发标志位,需要获取的数据,储存的数据结构,可以正式开始编码了:

requests是第三方库,需要另外安装,其他的是内置模块,直接引入即可:

1 import requests2 from html.parser import HTMLParser3 from html.entities import name2codepoint4 import json
           

获取豆瓣首页源码:

1 r = requests.get('https://www.douban.com/', timeout = 3)
           

是的,通过 requests获取网页只需要一行代码,timeout为获取页面超时时间,通过 r.text 就是我们需要的html源码,r.encoding可以获取网页编码格式,当然requests还有其他的方法供我们使用,

如 带参数的url: r = requests.get(url, params={.....}),获取数据等

解析豆瓣首页源码:

HTMLParser 里已经封装好了针对html的各种事件处理,如 开始标签,结束标签,标签属性,标签文本,注释,特殊字符,不了解的可以看下这个:

https://www.liaoxuefeng.com/wiki/1016959663602400/1017784593019776,很简单很清晰

1 class MyHTMLParser(HTMLParser): 2 def __init__(self): 3 super().__init__() 4 # 是否开始解析 5 self._allowRun = False 6  7 # 创建dist备用:储存数据 8 self.hotList = {'data': []} 9 10 # 每一个 li 块数据储存11 self.listItem = {}12 13 # 当前解析标签类型的标志位14 self.tagType = ''15 16 # 开始标签及 标签属性17 def handle_starttag(self, tag, attrs):18 if tag == 'ul' and ('class', 'time-list') in attrs:19 self._allowRun = True20 21 # 若当前是开启解析状态22 if self._allowRun:23 if tag == 'a' and ('class', 'title') in attrs:24 self.tagType = 'a'25 for (key, value) in attrs:26 if key == 'href':27 self.listItem[key] = value28 if tag == 'img':29 for (key, value) in attrs:30 if key == 'src':31 self.listItem['imgUrl'] = value32 33 if tag == 'span':34 self.tagType = 'span'35 36 # 结束标签37 def handle_endtag(self, tag):38 self.tagType = ''39 if tag == 'ul':40 self._allowRun = False41 42 if tag == 'li':43 if len(self.listItem) != 0:44 self.hotList['data'].append(self.listItem)45 self.listItem = {}46 47 # 空标签及 标签属性48 def handle_startendtag(self, tag, attrs):49 if self._allowRun:50 if tag == 'img':51 for (key, value) in attrs:52 if key == 'src':53 self.listItem['imgUrl'] = value54 55 # 标签文本56 def handle_data(self, data):57 if self._allowRun:58 if self.tagType == 'a':59 self.listItem['title'] = data60 self.taga = False61 elif self.tagType == 'span':62 self.listItem['type'] = data63 64 # 注释65 def handle_comment(self, data):66 pass67 68 # HTML entity 字符69 def handle_entityref(self, name):70 pass71 72 # Numeric 字符73 def handle_charref(self, name):74 pass75 76 parser = MyHTMLParser()77 parser.feed(r.text)
           

代码说明:我们必须知道在解析过程中,实例方法是按照源码顺序循环执行的,也就是说在同一个实例方法里,我们可以针对不同的标签或其他条件来进行不同的操作。我们所有的解析操作都是针对 ul.time-list 代码块的,所以我们需要一个开关,当前代码是 ul.time-list时才执行我们自定义的解析操作,这个开关就是上面代码里的 _allowRun,当开始标签是 ul.time-list的是否为 True,当结束标签是 ul 的是否为False,而只有当 _allowRun 为 True的时候,我们才继续解析当前的标签是 a 还是 img 或者 span。由于我们要在 文本解析事件 handle_data 中获取 a 标签的文本作为字段 title 的值,span标签的文本作为字段 type 的值,所以我们需要一个标志位变量来供我们在执行 handle_data 的时候判断当前解析的文本是属于 a 还是 span,这个标志位变量就是上面代码中 tagType,在 handle_starttag 中赋值,在 handle_endtag 中清空。我们将每一条数据储存在 listItem 中,当结束标签为 li 时,说明我们的对一个 li 代码块解析完毕,listItem 储存了我们需要的单挑数据,将 listItem 添加到 hotList中并清空 listItem 。执行上面代码,我们已经将数据储存在实例属性 hotList里面,我们可以在终端输出 parser.hotList:

python爬取ul下的li是空的_python爬取豆瓣首页热门栏目详细流程

储存数据

接下来就是将数据储存到本地文件中,而写入数据也是非常简单:

1 with open('hotList.json', 'w') as f:2 json.dump(parser.hotList, f)
           

在当前目录里打开 hotList.json 文件,可以看到如下数据:

python爬取ul下的li是空的_python爬取豆瓣首页热门栏目详细流程

数据倒是写入了,但是中文却没有如愿显示,而且对于追求美观的我们来说也无法接受,所以我们需要指定写入编码格式,以及格式化:

1 with open('hotList.json', 'w', encoding="utf-8") as f:2 json.dump(parser.hotList, f, ensure_ascii = False, indent = 4)
           

我们在写入的时候指定编码格式为 utf-8: encoding="utf-8",在 json.dump写入数据时增加了两个参数:ensure_ascii = False 禁止进行 ascii转码,indent = 4:按缩进为 4个单位格式化数据,当然我们还可以将字段进行排序,只需要加上字段:sort_keys = True,按需选择即可,再打开 hotList.json 文件查看:

1 { 2 "data": [ 3 { 4 "imgUrl": "https://img1.doubanio.com/dae/niffler/niffler/images/1c6e77ec-c493-11e9-84c0-0242ac110008.jpg