web挖掘
FB招聘站
分类阅读
专栏
公开课
FIT 2019
企业服务
用户服务
搜索
投稿
登录
注册
Python爬虫开发(二):整站爬虫与Web挖掘 VillanCh2016-02-24现金奖励共981040人围观 ,发现 37 个不明物体 WEB安全头条
*原创作者:VillanCh
文章合集:
Python爬虫开发(一):零基础入门 Python爬虫开发(二):整站爬虫与Web挖掘
Python爬虫开发(三):数据存储以及多线程
Python爬虫开发(三-续):快速线程池爬虫
Python爬虫开发(四):动态加载页面的解决方案与爬虫代理 Python爬虫开发(五):反爬虫措施以及爬虫编写注意事项
0×00 介绍
0×01 协议
0×02 原则
0×03 确立目标与分析过程
0×04 动手
0×05 sitemap爬虫
0×06 web元素处理
0×07 总结与预告
0×00 介绍
在互联网这个复杂的环境中,搜索引擎本身的爬虫,出于个人目的的爬虫,商业爬虫肆意横行,肆意掠夺网上的或者公共或者私人的资源。显然数据的收集并不是为所欲为,有一些协议或者原则还是需要每一个人注意。本文主要介绍关于爬虫的一些理论和约定协议,然后相对完整完成一个爬虫的基本功能。
0×01 协议
一般情况下网站的根目录下存在着一个robots.txt的文件,用于告诉爬虫那些文件夹或者哪些文件是网站的拥有者或者管理员不希望被搜索引擎和爬虫浏览的,或者是不希望被非人类的东西查看的。但是不仅仅如此,在这个文件中,有时候还会指明sitemap的位置,爬虫可以直接寻找sitemap而不用费力去爬取网站,制作自己的sitemap。那么最好我们看个具体的例子吧,这样更有助于理解robots协议:
-----------------------以下时freebuf的robots.txt-------------
User-agent: *
Disallow: /*?*
Disallow: /trackback
Disallow: /wp-*/
Disallow: */comment-page-*
Disallow: /*?replytocom=*
Disallow: */trackback
Disallow: /?random
Disallow: */feed
Disallow: /*.css$
Disallow: /*.js$
Sitemap: http://www.freebuf.com/sitemap.txt
大家可以看到,这里指明了适用的User-agent头,指明了Disallow的目录,也指明了sitemap,然后我们在看一下sitemap中是什么:
b1.png
我们大致可以发现这些都是整个网站允许公开的内容,如果这个爬虫作者是对freebuf的文章感兴趣的话,大可不必从头到尾设计爬虫算法拿下整个网站的sitemap,这样直接浏览sitemap节省了大量的时间。
0×02 原则
如果协议不存在的话,我们仍然不能为所欲为,上网随意搜索一下源于爬虫协议的官司,国内外都有。爬虫的协议规则建立在如下的基础上:
1. 搜索技术应该服务于人类,尊重信息提供者的意愿,并维护其隐私权;
2. 网站也有义务保护其使用者的个人信息和隐私不被侵犯。
简单来说,就是构建的爬虫以信息收集为目的是没错的,但是不能侵犯别人的隐私,比如你扫描并且进入了网站的robots中的disallow字段,你就可能涉及侵犯别人隐私的问题。当然作为一般人来讲,我们使用爬虫技术无非是学习,或者是搜集想要的信息,并没有想那么多的侵权,或者是商业的问题。
0×03 确立目标与分析过程
这里我提供一个爬虫诞生要经历的一般过程:
1. 确立需求在,sitemap中挑选出需要挖掘的页面;
2. 依次分析挑选出的页面;
3. 存储分析结果。
但是有时候问题就是,我们的目标网站没有提供sitemap,那么这就得麻烦我们自己去获取自己定制的sitemap。
目标:制作一个网站的sitemap:
分析:我们要完成这个过程,但是不存在现成sitemap,笔者建议大家把这个网站想象成一个图的结构,网站的url之间纵横交错,我们可以通过主页面进行深度优先或者广度优先搜索从而遍历整个网站拿到sitemap。显然我们发现,我们首先要做的第一步就是完整的获取整个页面的url。
但是我们首先得想清楚一个问题:我们获取到的url是应该是要限制域名的,如果爬虫从目标网站跳走了,也就意味着将无限陷入整个网络进行挖掘。这么庞大的数据量,我想不是一般人的电脑硬盘可以承受的吧!
那么我们的第一步就是编写代码去获取整个页面的url。其实这个例子在上一篇文章中已经讲到过。
0×04 动手
我们的第一个小目标就是获取当前页面所有的url:
首先必须说明的是,这个脚本并不具有普遍性(只针对freebuf.com),并且效率低下,可以优化的地方很多:只是为了方便,简单实现了功能,有兴趣的朋友可以任意重构达到高效优雅的目的
import urllib
from bs4 import BeautifulSoup
import re
def get_all_url(url):
urls = []
web = urllib.urlopen(url)
soup =BeautifulSoup(web.read())
#通过正则过滤合理的url(针对与freebuf.com来讲)
tags_a =soup.findAll(name='a',attrs={'href':re.compile("^https?://")})
try :
for tag_a in tags_a:
urls.append(tag_a['href'])
#return urls
except:
pass
return urls
#得到所有freebuf.com下的url
def get_local_urls(url):
local_urls = []
urls = get_all_url(url)
for _url in urls:
ret = _url
if 'freebuf.com' in ret.replace('//','').split('/')[0]:
local_urls.append(_url)
return local_urls
#得到所有的不是freebuf.com域名的url
def get_remote_urls(url):
remote_urls = []
urls = get_all_url(url)
for _url in urls:
ret = _url
if "freebuf.com" not in ret.replace('//','').split('/')[0]:
remote_urls.append(_url)
return remote_urls
def __main__():
url = 'http://freebuf.com/'
rurls = get_remote_urls(url)
print "--------------------remote urls-----------------------"
for ret in rurls:
print ret
print "---------------------localurls-----------------------"
lurls = get_local_urls(url)
for ret in lurls:
print ret
if __name__ == '__main__':
__main__()
这样我们就得到了该页面的url,本域名和其他域名下:
b2.png
b3.png
当然图中所示的结果为部分截图,至少证明了我们上面的代码比较好的解决的url获取的问题,由于这里我们的判断规则简单,还有很多情况没有考虑到,所以建议大家如果有时间,可以把上面代码重构成更佳普适,更加完整的脚本,再投入真正的实际使用。
0×05 sitemap爬虫
所谓的sitemap,我们在上面的例子中又讲到sitemap记录了整个网站的网站拥有者允许你爬取内容的链接。但是很多情况下,sitemap没有写出,那么问题就来了,我们面对一个陌生的网站如何进行爬取(在不存在sitemap的情况下).
当然我们首先要获取sitemap,没有现成的我们就自己动手获取整个网站的sitemap。
再开始之前我们需要整理一下思路:我们可以把整站当成一个错综复杂的图结构,有一些算法基础的读者都会知道图的简单遍历方法:dfs和bfs(深度优先和广度优先)。如果这里读者有问题的话建议先去学习一下这两种算法。大体的算法结构我们清楚了,但是在实现中我们显然需要特殊处理url,需要可以区分当前目标站点域名下的网站和其他域名的网站,除此之外,在href的值中经常会出现相对url,这里也要特别处理。
import urllib
from bs4 import BeautifulSoup
import urlparse
import time
import urllib2
url = "http://xxxx.xx/"
domain = "xxxx.xx"
deep = 0
tmp = ""
sites = set()
visited = set()
#local = set()
def get_local_pages(url,domain):
global deep
global sites
global tmp
repeat_time = 0
pages = set()
#防止url读取卡住
while True:
try:
print "Ready to Open the web!"
time.sleep(1)
print "Opening the web", url
web = urllib2.urlopen(url=url,timeout=3)
print "Success to Open the web"
break
except:
print "Open Url Failed !!! Repeat"
time.sleep(1)
repeat_time = repeat_time+1
if repeat_time == 5:
return
print "Readint the web ..."
soup = BeautifulSoup(web.read())
print "..."
tags = soup.findAll(name='a')
for tag in tags:
#避免参数传递异常
try:
ret = tag['href']
except:
print "Maybe not the attr : href"
continue
o = urlparse.urlparse(ret)
"""
#Debug I/O
for _ret in o:
if _ret == "":
pass
else:
print _ret
"""
#处理相对路径url
if o[0] is "" and o[1] is "":
print "Fix Page: " +ret
url_obj = urlparse.urlparse(web.geturl())
ret = url_obj[0] + "://" + url_obj[1] + url_obj[2] + ret
#保持url的干净
ret = ret[:8] + ret[8:].replace('//','/')
o = urlparse.urlparse(ret)
#这里不是太完善,但是可以应付一般情况
if '../' in o[2]:
paths = o[2].split('/')
for i inrange(len(paths)):
if paths[i] == '..':
paths[i] = ''
if paths[i-1]:
paths[i-1] = ''
tmp_path = ''
for path in paths:
if path == '':
continue
tmp_path = tmp_path + '/' +path
ret =ret.replace(o[2],ret_path)
print "FixedPage: " + ret
#协议处理
if 'http' not in o[0]:
print "Bad Page:" + ret.encode('ascii')
continue
#url合理性检验
if o[0] is "" and o[1] is not "":
print "Bad Page: " +ret
continue
#域名检验
if domain not in o[1]:
print "Bad Page: " +ret
continue
#整理,输出
newpage = ret
if newpage not in sites:
print "Add New Page: " + newpage
pages.add(newpage)
return pages
#dfs算法遍历全站
def dfs(pages):
#无法获取新的url说明便利完成,即可结束dfs
if pages is set():
return
global url
global domain
global sites
global visited
sites = set.union(sites,pages)
for page in pages:
if page not in visited:
print "Visiting",page
visited.add(page)
url = page
pages = get_local_pages(url, domain)
dfs(pages)
print "sucess"
pages = get_local_pages(url, domain)
dfs(pages)
for i in sites:
print i
在这个脚本中,我们采用了dfs(深度优先算法),关于算法的问题我们不做深入讨论,实际上在完成过程中我们关键要完成的是对href的处理,在脚本代码中我们也可以看到,url的处理占了大部分代码,但是必须说明的是:很遗憾我们并没有把所有的情况都解决清楚,但是经过测试,上面的代码可以应付很多种情况。所以大家如果有需要可以随意修改使用。
测试:用上面的脚本在网络状况一般的情况下,对我的个人博客(大概100个页面)进行扫描,大概用时2分半,这是单机爬虫。
0×06 Web元素处理
在接下来的这个例子中,我们不再关注url的处理,我们暂且把目光对准web单个页面的信息处理。按照爬虫编写的一般流程,在本例子中,争取每一步都是完整的可操作的。
确立目标:获取freebuf的文章并且生成docx文档。
过程:侦察目标网页的结构,针对特定结构设计方案
脚本实现:bs4模块和docx模块的使用
预备知识:
1.Bs4的基本api的使用,关于beautifulSoup的基本使用方法,我这里需要介绍在下面的脚本中我使用到的方法:
Soup = BeautifulSoup(data)#构建一个解析器
Tags = Soup.findAll(name,attr)
我们重点要讲findAll方法的两个参数:name和attr
Name: 指的是标签名,传入一个标签名的名称就可以返回所有固定名称的标签名
Attr: 是一个字典存储需要查找的标签参数,返回对应的标签
Tag.children 表示获取tag标签的所有子标签
Tag.string 表示获取tag标签内的所有字符串,不用一层一层索引下去寻找字符串
Tag.attrs[key] 表示获取tag标签内参数的键值对键为key的值
Tag.img 表示获取tag标签的标签名为img的自标签(一个)
在本例子的使用中,笔者通过标签名+id+class来定位标签,虽然这样的方法不是绝对的正确,但是一般情况下,准确率还是比较高的。
2.docx的使用:在使用这个模块的时候,要记清楚如果:
pip install python-docx
easy_install python-docx
两种方式安装都不能正常使用的话,就需要下载tar包自己手动安装
Docx模块是一个可以直接操作生成docx文档的python模块,使用方法极尽简单:
Demo = Document() #在内存中建立一个doc文档
Demo.add_paragraph(data) #在doc文档中添加一个段落
Demo.add_picture(“pic.png”) #doc文档中添加一个图片
Demo.save(‘demo.docx’) #存储docx文档
当然这个模块还可以操作段落的字体啊格式啊各种强大的功能,大家可以从官方网站找到对应的文档。鉴于我们以学习为目的,我就不作深入的排版介绍了
开始:
观察html结构:
b4.png
我们大致观察一下结构,定位到文章的具体内容需要找到标签,然后再遍历标签的子标签即可遍历到所有的段落,配图资料
b5.png
这样定位到图片,那么我们怎么样来寻找
代码
from docx import Document
from bs4 import BeautifulSoup
import urllib
url ="http://freebuf.com/news/94263.html"
data = urllib.urlopen(url)
document = Document()
soup = BeautifulSoup(data)
article = soup.find(name ="div",attrs={'id':'contenttxt'}).children
for e in article:
try:
if e.img:
pic_name = ''
print e.img.attrs['src']
if 'gif' in e.img.attrs['src']:
pic_name = 'temp.gif'
elif 'png' in e.img.attrs['src']:
pic_name = 'temp.png'
elif 'jpg' in e.img.attrs['src']:
pic_name = 'temp.jpg'
else:
pic_name = 'temp.jpeg'
urllib.urlretrieve(e.img.attrs['src'], filename=pic_name)
document.add_picture(pic_name)
except:
pass
if e.string:
print e.string.encode('gbk','ignore')
document.add_paragraph(e.string)
document.save("freebuf_article.docx")
print "success create a document"
当然上面的代码,读者看起来是非常粗糙的,其实没关系,我们的目的达到了,在使用中学习,这也是python的精神。可能大家不熟悉beautifulsoup的使用,这里需要大家自行去读一下beautifulsoup官方的doc,笔者不知道有没有中文版,但是读英文也不是那么麻烦,基本写的还是非常简明易懂。
那么我们可以看一下输出的结构吧!
b6.png
这是文件夹目录下的文件,打开生成的docx文档以后:
b7.png
b8.png
这里我们发现排版仍然是有缺陷,但是所有的内容,图片都按照原来的顺序成功存储在了.docx文件中。
0×07 总结与预告
在本文中我们快速学习了简单单机爬虫的制作,并且动手对网页的信息进行了一定的处理分类。但是这并不是结束,而仅仅是一个开始。还有很多的知识等待我们去探究:比如模拟登陆,抓取登陆以后的web网页等,其实这些并不困难。
在接下来的文章中我们会陆续讲到:
1. 数据存储
2. 多线程
3. 动态网页抓取(js加载)
*原创作者:VillanCh,本文属FreeBuf原创奖励计划文章,未经作者本人及FreeBuf许可,切勿私自转载
VillanCh
VillanCh
22 篇文章
等级: 5级
||
上一篇:Python爬虫开发(一):零基础入门下一篇:如何编写自己的Web日志分析脚本?
发表评论已有 37 条评论
Twilight2015 (1级) 2016-02-24回复 1楼
那个robots.txt根目录在哪找?
亮了(0)
VillanCh 2016-02-24回复
@ Twilight2015 你输入http://freebuf.com/robots.txt 看一下, 如果存在的话 就是http://域名/robots.txt
亮了(0)
Hi2vd (4级) 某甲方乙方最讨厌的人······ 2016-02-24回复 2楼
学习了,希望有可以py3.x的版本的
亮了(4)
warcraft23 (1级) 2016-02-24回复 3楼
期待下一篇文章关于多线程以及动态抓取的,在我写的爬虫里使用多线程,就感觉抓取的网页不全;动态抓取js生成的内容采用什么框架我不清楚。顺带一问,抓取的信息已经存在了数据库后,想分析数据库的数据,然后生成饼图啊柱状图啥的,python里有没有好用的包呢
亮了(0)
VillanCh 2016-02-24回复
@ warcraft23 目前我正在编写这一部分,现在有个仍然存在bug的版本,如果你需要你可以拿去fork改改用了。
动态抓取的话我推荐是selenium的方案,稍后的文章会介绍的
亮了(0)
木千之 (5级) 人,既无虎狼之爪牙,亦无狮象之力量,却能擒狼缚虎,驯狮猎象,... 2016-02-24回复 4楼
好文章,喜欢Python,终于可以系统学习下爬虫怎么写了,赞一个~~
亮了(0)
xixuedafa123 (2级) 2016-02-24回复 5楼
那个…..楼主
for i inrange(len(paths)): in和range中间有空格的。
还有几个缩进估计是编辑器的问题…….
首先赞下,正儿八经的干货,感谢楼主,感谢FREEBUF
我试了几个不同的站,问题都出在,
sites = set.union(sites,pages)
TypeError: ‘NoneType’ object is not iterable
所调用的函数的返回的值的类型,是否和返回值所赋值的变量的类型,两者是否匹配。—网上翻的,还需要处理下呢。
亮了(0)
VillanCh 2016-02-24回复
@ xixuedafa123 感谢指出, 我核实以下马上改一下啊
亮了(0)
xixuedafa123 (2级) 2016-02-24回复
@ VillanCh 我发现e.string不包含文章里的python代码部分,那东西是可能需要用正则获取了对不?
亮了(0)
VillanCh 2016-02-24回复
@ xixuedafa123 这个需要看具体文章的html元素分析一下,我没有看code部分的元素,也许你可以针对所有的代码部分写以下分析过程
亮了(0)
xixuedafa123 (2级) 2016-02-24回复
@ VillanCh 谢谢啦,sites = set.union(sites,pages)部分咋修改哦。就是TypeError: ‘NoneType’ object is not iterable的。
亮了(0)
VillanCh 2016-02-24回复
@ xixuedafa123 这个代码的意思就是合并这两个set,这里如果错误的话,你可以手动循环其中一个集合,逐个添加到另一个集合里面,最原始的办法:
for page in pages:
sites.add(page)
亮了(0)
银月城 (1级) 2016-03-09回复
@ xixuedafa123 你的问题解决了吗 我按照楼主说的将sites = set.union(sites,pages)变换成for循环 但还是会报出TypeError: ‘NoneType’ object is not iterable 不知道什么原因 知道的话可以告诉一下我吗?
亮了(0)
dyonroot (1级) 2016-03-17回复
@ xixuedafa123 这部分其实是dfs()函数最开始判断pages为空的时候出的问题,if pages is set(): return,原意是pages是空的时候要返回,但是不知道什么原因这里不会返回,结果把空的pages传递下去,导致遍历空的set()才出的问题。 我实验的时候,这里改成 if not pages: return 就解决的这个问题。 然后set union的地方,官方文档里union的用法是 sites.union(pages)。
亮了(4)
xixuedafa123 (2级) 2016-06-22回复
@ dyonroot 回复迟了,兄弟,确实有效,谢谢!
亮了(0)
iggy 2016-02-24回复 6楼
相对连接可以用urljoin来处理
亮了(0)
Z-猪小姐 2016-02-24回复 7楼
[喵喵]
亮了(0)
prompt (1级) 想要吃成个胖纸~ 2016-02-24回复 8楼
find_all,旧版本的是findAll
亮了(0)
test 2016-02-25回复 9楼
看来有很多人都对爬虫感兴趣
亮了(1)
目语声 (1级) 2016-02-29回复 10楼
之前爬虫一的实现了非常感谢您。然后就装不上docx,还有引用lxml也会报错。怎么破。
亮了(0)
VillanCh (5级) Be quiet and listen more! 2016-02-29回复
@ 目语声 我在win下安装docx的时候也出了问题,后来解决办法是,直接下载模块解压以后cmd运行python install.py手动安装
不使用pip和easy_install,感谢支持
亮了(0)
散步的蜗牛 2016-03-02回复 11楼
示例程序有误吧 ret_path 在哪定义呢?
亮了(1)
VillanCh (5级) Be quiet and listen more! 2016-03-02回复
@ 散步的蜗牛 感谢指出!是tmp_path
亮了(1)
monk 2016-03-03回复 12楼
article = soup.find(name="div",attrs={‘id’:'contenttxt’}).children
AttributeError: ‘NoneType’ object has no attribute ‘children’
请问怎么破
亮了(0)
monk 2016-03-03回复
@ monk 不好意思 犯了低级错误
亮了(0)
别太咸 2016-03-03回复 13楼
sites = set.union(sites,pages)
TypeError: ‘NoneType’ object is not iterable
还是这个问题啊。。
亮了(0)
庸俗的螃蟹 (1级) 2016-03-07回复
@ 别太咸 我也是这个错误,代码能看懂,但是就是这里挑不出来。
亮了(0)
芜湖四海 (1级) 2016-04-14回复
@ 庸俗的螃蟹 我把 if pages is set(): 改为 if pages==None 成功了
亮了(0)
庸俗的螃蟹 (1级) 2016-03-07回复 14楼
我感觉 sites 是none啊
导致了 Typenone的问题。下面好多人都这样说,
也不知道是不是我的饿代码 打错了。
亮了(0)
夲跑的小蜗牛 (1级) 2016-03-10回复 15楼
打印出来的时候,貌似只打印了div标签下的第一个部分,后面的部分貌似都没获取到
亮了(0)
稻草小人A (1级) 2016-03-25回复 16楼
0×05 sitemap爬虫
这里面的代码
为什么需要visit?
能被加入pages不都是sites中没有的么 ,只要没有的就应该visit。
求指教0 0
亮了(0)
VillanCh (5级) Be quiet and listen more! 2016-03-25回复
@ 稻草小人A 不一定啊,同一个url被重复包含在很多页面里面的时候就不好讲了。
亮了(0)
TLW (1级) 2016-04-24回复
@ VillanCh 那个dfs在for循环中多加一句newpage=get_local_pages(url, domain)
try:
newpages =set.union(newpages,newpage )
except:
pass
直接用dfs(newpages)循环
url输出要么是爬完的sites要么visiting这句话输出url
虽然有点慢,但是你的问题解决了
亮了(0)
芜湖四海 (1级) 2016-04-14回复 17楼
感谢博主分享,我在调试的过程中也发生了
TypeError: ‘NoneType’ object is not iterable
而且是在五次尝试链接失败,然后返回了一个空的pages
在 sites=sites|pages 时候出现问题,那是不是要先判断pages为空啊
if pages is set():这句是用来干嘛的?是来判断的吗
亮了(0)
芜湖四海 (1级) 2016-04-14回复
@ 芜湖四海 我知道了 我把 if pages is set(): 改为 if pages==None 成功了
亮了(0)
卖卖 (1级) 2017-04-27回复 18楼
一直是
UnboundLocalError: local variable ‘paths’ referenced before assignment
这个错误,这么搞定啊?
亮了(2)
mayboyxxx (1级) 2017-08-19回复
@ 卖卖 是代码锁进的问题,你调一下就知道了
亮了(0)
昵称
请输入昵称
必须您当前尚未登录。登陆?注册邮箱
请输入邮箱地址
必须(保密)表情插图
有人回复时邮件通知我
VillanCh
VillanCh
Be quiet and listen more!
22
文章数
31
评论数
最近文章
TDD 方法开发渗透测试工具:代理扫描器(第二集)
2016.12.12
TDD 方法开发渗透测试工具:代理扫描器(第一集)
2016.12.09
PHP Trick 总结与探讨(一)
2016.11.18
浏览更多
相关阅读
利用Python实现DGA域名检测PlaidCTF 2017 web writeupIDAPython:让你的生活更美好(四)短信炸弹—用Python模拟ajax请求Parator:基于python的多线程爆破小工具
特别推荐
关注我们 分享每日精选文章
活动预告
11月
FreeBuf精品公开课·双11学习狂欢节 | 给努力的你打打气
已结束
10月
【16课时-连载中】挖掘CVE不是梦(系列课程2)
已结束
10月
【首节课仅需1元】挖掘CVE不是梦
已结束
9月
【已结束】自炼神兵之自动化批量刷SRC
已结束
FREEBUF免责声明协议条款关于我们加入我们广告及服务寻求报道广告合作联系我们友情链接关注我们
官方微信
新浪微博腾讯微博Twitter赞助商
Copyright © 2018 WWW.FREEBUF.COM All Rights Reserved 沪ICP备13033796号
css.php0daybank
文章评论