Python爬虫实践——简单爬取我的博客
学任何一门技术,如果没有实践,技术就难以真正的吸收。利用上次博客讲解的三个知识点:URL 管理器、网页下载器和网页解析器来爬取一下我的博客。
我的博客地址
http://weaponzhi.online/
这个博客里面没有技术的文章,主要是我的一些生活上面的记录,可以说是我的日记本,平时会写一些思想感悟,记录些琐事。我们简单以这个博客主页为入口,爬取一下以 weaponzhi.online 为 host 下所有的 URL 。
首先当然是需要一个 URL 管理器了,但和上篇文章说的有所不同,这次我们的待爬取数据结构是队列,实际上 Python 本身的 list 已经可以实现队列的一些操作了,但 list 的一些队列操作比如 pop() 效率是比较低的,Python 官方推荐使用 collection.deque 来完成队列操作。
from collections import deque
queue = deque() # 待爬取的队列
visited = set() # 已爬取的set
host = 'http://weaponzhi.online' # 入口页面, 可以换成别的
queue.append(host)
host 是我们的入口界面同时也作为一个 host 作为后续 URL 拼接使用,这个到后面再说。
count = 0 # 计数器
while queue:
url = queue.popleft() # 队首元素出队
visited.add(url) # 标记为已访问
print('已经抓取: ' + str(count) + ' 正在抓取 <--- ' + url)
count += 1
try:
url_response = urllib.request.urlopen(url)
# 过滤.jpg这种非跳转URL的情况
if 'html' not in url_response.getheader('Content-Type'):
continue
data = url_response.read().decode('utf-8')
except:
continue
URL 管理器相关的数据结构建立好后,我们就可以从入口开始进行我们网页下载器部分的编写了。
我们先从待爬取的队列中 pop 出一个 URL,注意,pop 开头的方法在获取元素的同时会将元素从队列中移除。当我们获取完待爬取的元素的同时也将该 URL 添加到已爬取 URL 容器管理。
因为我们是一个简单的爬虫,并没有作任何的异常处理,所以为了避免爬取过程中遇到一些网络异常状况导致爬虫程序终止,我们将网页下载器的关键代码部分都 try...except 了,在遇到特殊情况的时候,将继续循环流程。
这里处理了一种情况,因为有的 URL 是一个下载链接或者图片,以 .jpg 或者 .ico 结尾,这种情况我们应该通过判断 header 属性机智的跳过它。
到现在为止都是上一篇文章的内容,不是很难,最复杂和需要花时间处理逻辑的,还是我们的网络解析器部分。
我先小试牛刀,在刚刚的 while 语句下写下了这段代码。
soup = BeautifulSoup(data, 'lxml')
node = soup.find_all('a') # 查找a标签节点
for x in node:
try:
new_url = x['href']
if new_url not in queue and new_url not in visited:
queue.append(new_url)
print('加入队列 ---> ' + new_url)
except:
continue
我使用 lxml 作为解析器,这种解析器的速度比 html.parser更快,而且它在 Python 各平台的兼容性也非常好。
我们通过 find_all 拿到了一个节点列表,需要注意的是,通过 soup 对象 find 函数返回的对象类型是bs4.element.Tag,Tag 与 XML 和 HTML 原生文档中的 tag 相同,它有两个重要属性 Name 和 Attributes,在这个例子中,name 就是标签名 a,Attributes 有很多,比如这里的 href,还有 class,id 等等。Attributes 的使用方法和字典一模一样,比如 x['href']。
上面的解析器代码输出的结果如下图所示
从结果来看这个爬虫还是有一定问题的,首先,爬取的 URL 大多都是相对路径,这种路径使用 urlopen 是肯定没法读取的。其次,我们看到我们会爬取到一些和博客地址无关的路径,比如图中的知乎地址,出现这种情况的原因是博客会有一些路径的跳转入口,如果我们放任爬虫去爬取这些路径,除非我们限定了爬取数量,那么爬虫将会无限制的爬取下去,直到天荒地老。
我们的目的是只爬取以 weaponzhi.online 开头的博客内地址,并且记录爬取数,如果队列中的 URL 全部出队,则自动停止循环,修改后的代码如下所示
for x in node:
try:
new_url = x['href']
if new_url.startswith('/'):
new_url = host + new_url
if new_url not in queue and new_url not in visited:
if 'http' in new_url and 'weaponzhi' in new_url:
queue.append(new_url)
print('加入队列 ---> ' + new_url)
except:
continue
我这里的处理方式比较简单粗暴,如果 URL 是以 '/' 开头的,我就认为它是一个相对地址,并把该地址与 host 地址作一个拼接。然后,用一个判断语句,确保该 URL 内有 weaponzhi 字段,从而保证了 URL 是博客内地址。当然了,更好的方式是使用正则表达式来代替这些判断语句,这就留给各位看官自行思考了~
来看看现在的代码结果
我将源码上传到了 Github 上,以后相关的文章都会在这个仓库中拓展。
https://github.com/WeaponZhi/PythonSpiner
- NVIDIA把消费级显卡拒绝在数据中心门外
- 保护好手中的比特币,比特币诈骗又见新花样!
- 传万达网络科技集团将大幅减员90% 业务收缩恐为主因
- 企业技术展望:黑科技解放“黄金”劳动力
- 比特币的SegWit2X分叉将于12月28日重新启动
- 2017年让运营人扎心的30条文案
- R基础——数据的导入与导出(下)
- 深入机器学习系列12-高斯混合模型
- AI时代,教育工作者必备的3大技能
- 邮箱数据防泄漏系统建设
- 你的小程序总是被人吐槽?腾讯做的这款小程序,帮你收集用户意见
- 漫话2017之科技那些事儿
- 代码注入技术Process Doppelgänging利用介绍
- 安卓手机如何玩转动作手势检测?有TensorFlow就够了,附实用教程
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- 操作系统实验多线程编程中的读者优先和写者优先
- 【python】使用csv库以字典格式读写csv文件
- 基于TypeScript封装Axios笔记(八)
- springmvc之HttpMessageConverter<T>
- django-模板之静态文件加载(十四)
- springmvc之使用JstlView
- django-模板之include标签(十五)
- 【pytorch】改造mobilenet_v2进行multi-class classification(多标签分类)
- 走进STL - heap,小树芽
- 走进STL - 序列式容器(常用篇)
- springmvc之RequestMapping中的请求方式
- 拥抱STL - union,天作之秀
- 拥抱STL -typename该怎么理解
- 走近STL - map,只愿一键对一值
- springmvc之使用servlet原生API作为参数