记录一下爬取豆瓣热门专栏的经过, 通过这篇文章, 你能学会 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/ , 分析一下我们需要爬取模块的代码:
可以看到, 我们需要爬取的数据都在 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 是第三方库, 需要另外安装, 其他的是内置模块, 直接引入即可:
- import requests
- from HTML.parser import HTMLParser
- from HTML.entities import name2codepoint
- 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 的各种事件处理, 如 开始标签, 结束标签, 标签属性, 标签文本, 注释, 特殊字符, 不了解的可以看下这个:
, 很简单很清晰
- class MyHTMLParser(HTMLParser):
- def __init__(self):
- super().__init__()
- # 是否开始解析
- self._allowRun = False
- # 创建 dist 备用: 储存数据
- self.hotList = {'data': []}
- # 每一个 li 块数据储存
- self.listItem = {}
- # 当前解析标签类型的标志位
- self.tagType = ''
- # 开始标签及 标签属性
- def handle_starttag(self, tag, attrs):
- if tag == 'ul' and ('class', 'time-list') in attrs:
- self._allowRun = True
- # 若当前是开启解析状态
- if self._allowRun:
- if tag == 'a' and ('class', 'title') in attrs:
- self.tagType = 'a'
- for (key, value) in attrs:
- if key == 'href':
- self.listItem[key] = value
- if tag == 'img':
- for (key, value) in attrs:
- if key == 'src':
- self.listItem['imgUrl'] = value
- if tag == 'span':
- self.tagType = 'span'
- # 结束标签
- def handle_endtag(self, tag):
- self.tagType = '' if tag =='ul':
- self._allowRun = False
- if tag == 'li':
- if len(self.listItem) != 0:
- self.hotList['data'].append(self.listItem)
- self.listItem = {}
- # 空标签及 标签属性
- def handle_startendtag(self, tag, attrs):
- if self._allowRun:
- if tag == 'img':
- for (key, value) in attrs:
- if key == 'src':
- self.listItem['imgUrl'] = value
- # 标签文本
- def handle_data(self, data):
- if self._allowRun:
- if self.tagType == 'a':
- self.listItem['title'] = data
- self.taga = False
- elif self.tagType == 'span':
- self.listItem['type'] = data
- # 注释
- def handle_comment(self, data):
- pass
- # HTML entity 字符
- def handle_entityref(self, name):
- pass
- # Numeric 字符
- def handle_charref(self, name):
- pass
- parser = MyHTMLParser()
- 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:
储存数据
接下来就是将数据储存到本地文件中, 而写入数据也是非常简单:
- with open('hotList.json', 'w') as f:
- JSON.dump(parser.hotList, f)
在当前目录里打开 hotList.JSON 文件, 可以看到如下数据:
数据倒是写入了, 但是中文却没有如愿显示, 而且对于追求美观的我们来说也无法接受, 所以我们需要指定写入编码格式, 以及格式化:
- with open('hotList.json', 'w', encoding="utf-8") as f:
- 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 文件查看:
- {
- "data": [
- {
- "imgUrl": "https://img1.doubanio.com/dae/niffler/niffler/images/1c6e77ec-c493-11e9-84c0-0242ac110008.jpg",
- "href": "https://m.douban.com/time/column/164?dt_time_source=douban-web_anonymous",
- "title": "伤别离与共春风 -- 唐宋词的情感世界",
- "type": "音频专栏"
- },
- {
- "imgUrl": "https://img1.doubanio.com/dae/niffler/niffler/images/511ccf86-b8fc-11e9-b188-0242ac110008.jpg",
- "href": "https://m.douban.com/time/column/163?dt_time_source=douban-web_anonymous",
- "title": "世界记忆大师教你快速提升记忆力",
- "type": "视频专栏"
- },
- {
- "imgUrl": "https://img1.doubanio.com/dae/niffler/niffler/images/74897a9e-880c-11e9-bd82-0242ac11001b.jpg",
- "href": "https://m.douban.com/time/column/159?dt_time_source=douban-web_anonymous",
- "title": "黑白之间: 二十八堂书法练习课",
- "type": "视频专栏"
- },
- {
- "imgUrl": "https://img3.doubanio.com/dae/niffler/niffler/images/6f488990-a773-11e9-b587-0242ac110011.jpg",
- "href": "https://m.douban.com/time/column/161?dt_time_source=douban-web_anonymous",
- "title": "马伯庸的冷门书单",
- "type": "音频专栏"
- },
- {
- "imgUrl": "https://img1.doubanio.com/dae/niffler/niffler/images/6c46cb9c-ac61-11e9-97e2-0242ac11000c.jpg",
- "href": "https://m.douban.com/time/column/162?dt_time_source=douban-web_anonymous",
- "title": "听! 解说式音乐会 -- 古典音乐聆听指南",
- "type": "视频专栏"
- },
- {
- "imgUrl": "https://img3.doubanio.com/dae/niffler/niffler/images/ebd421cc-9968-11e9-ad2c-0242ac110006.jpg",
- "href": "https://m.douban.com/time/column/158?dt_time_source=douban-web_anonymous",
- "title": "从格里菲斯到诺兰 -- 影迷都在看的电影结构大师课",
- "type": "视频专栏"
- },
- {
- "imgUrl": "https://img3.doubanio.com/dae/niffler/niffler/images/fa83f054-9633-11e9-a82e-0242ac110006.jpg",
- "href": "https://m.douban.com/time/column/157?dt_time_source=douban-web_anonymous",
- "title": "打开电影声音的魔盒 -- 好莱坞声音设计大师课",
- "type": "视频专栏"
- },
- {
- "imgUrl": "https://img3.doubanio.com/dae/niffler/niffler/images/81788c8e-8e53-11e9-b51e-0242ac110010.jpg",
- "href": "https://m.douban.com/time/column/156?dt_time_source=douban-web_anonymous",
- "title": "一剧之本 -- 好莱坞编剧教父大师课",
- "type": "视频专栏"
- },
- {
- "imgUrl": "https://img3.doubanio.com/dae/niffler/niffler/images/5d7d70aa-8b25-11e9-a08f-0242ac110012.jpg",
- "href": "https://m.douban.com/time/column/155?dt_time_source=douban-web_anonymous",
- "title": "老叶说电影 --90 分钟看懂中国电影产业",
- "type": "视频专栏"
- },
- {
- "imgUrl": "https://img3.doubanio.com/dae/niffler/niffler/images/e2e59078-828e-11e9-a465-0242ac110012.jpg",
- "href": "https://m.douban.com/time/column/154?dt_time_source=douban-web_anonymous",
- "title": "好莱坞特效大师课 -- 从概念艺术到 3D 建模",
- "type": "视频专栏"
- }
- ]
- }
这样就只有两个字: 嘘服.
总结
这个例子只是用来熟悉爬虫基本操作和思维逻辑, 真正用到项目中还是得结合其他框架, 如 Beautiful Soup, 就可以获取指定代码片段进行解析而不需要像我们上面那样设置开关或标志位. 有兴趣的朋友可以自己动手试试.
与诸君共勉.
来源: https://www.cnblogs.com/wbsndbf/p/11439415.html