概述
最近做计网大作业,要求实现一个爬虫抓取网络上关于人工智能的有关信息,于是就自己diy了一个,然后丢到服务器上跑,勉勉强强能达到十万级的数据量,也算能交差了。
下面就把实现过程记录一下,一来可以做个笔记,以免以后忘了怎么做,二来可以给有这方面困扰的人提供一点思路。
说到爬虫,要解决的无疑就那么几个问题:爬,取,过滤。
爬
先来说说爬,这就是一个搜索的过程,可以通过各种搜索算法来实现,这里用的是bfs,原理也很简单,找最短路嘛。起点就是我们提供的入口url列表,可以多选几个不同的网站,提供多样性,如:
#入口url列表
urls = [
'http://www.ailab.cn/news/ainew/',
'http://news.chinaso.com/search?wd='+request.quote('人工智能+行业'),
'https://www.baidu.com/s?ie=utf-8&cus_sid=18364444163281065044&tn=SE_pscse_053x7tyx&wd=%E4%BA%BA%E5%B7%A5%E6%99%BA%E8%83%BD%20%E8%A1%8C%E4%B8%9A'
'http://search.ifeng.com/sofeng/search.action?q=%E4%BA%BA%E5%B7%A5%E6%99%BA%E8%83%BD+%E8%A1%8C%E4%B8%9A&c=1&chel='
'http://search.southcn.com/web/search?channelid=216505&&searchword=%E4%BA%BA%E5%B7%A5%E6%99%BA%E8%83%BD%20%E8%A1%8C%E4%B8%9A',
'https://www.baidu.com/s?ie=utf-8&cus_f=0&wd=%E4%BA%BA%E5%B7%A5%E6%99%BA%E8%83%BD%01%E8%A1%8C%E4%B8%9A&tn=71032170_cpr&rsv_lu=3_sb&si=china.com&ct=2097152&fenlei=mv6qUAdxTZPxTvb0IZRqIHDLnjmdnWc0T1YYPARYrHN-nAR4uhD1uHTL0AGo5HmLPH7hP1u9PHD4Pj9bnhm0IAYqnWm3PjfkPHRz0APh5Hn0Thcqn0K_IyVG5HD0mv4YUWYLnH01nWDLn7qWTZc0TLPs5HD0TLPsnWYk0ZwYTjYk0A4YIZ0qnfKLpHYY0Au1mv9VujYz0Zwb5HDLnjmdnWc0IgF_5y9YIZ0-nYD-nbm-nbuYuyPCFHF7mv9GUhD-nbNWUvY-nbm0',
'http://search.sina.com.cn/?q=%C8%CB%B9%A4%D6%C7%C4%DC+%D0%D0%D2%B5&range=all&c=news&sort=rel',
'http://sou.chinanews.com.cn/search.do?q=%E4%BA%BA%E5%B7%A5%E6%99%BA%E8%83%BD+%E8%A1%8C%E4%B8%9A&ps=10&time_scope=0&channel=all&sort=_score',
'http://news.sogou.com/news?query=%C8%CB%B9%A4%D6%C7%C4%DC+%D0%D0%D2%B5',
'https://www.sogou.com/sogou?site=news.qq.com&query=%C8%CB%B9%A4%D6%C7%C4%DC+%D0%D0%D2%B5&pid=sogou-wsse-b58ac8403eb9cf17-0004&sourceid=&idx=f&idx=f'
]
终点就是我们感兴趣的网页,起点经过若干个站点跳转最终到达终点,而这些中间站点无疑是越少越好,最理想的情况自然是一步就能到达,当然这个可能性不大。之所以选用bfs,是因为其实现简单,找到终点也很快。但是有个问题:如果不加优化的话,很有可能会有一堆没用的(即你不感兴趣的)url占据你的队列,而你真正感性的那些却只能乖乖排队等着前面的那些一个个地被你丢弃,而这种现象越到后期(队列很长)越明显,最差的情况可能一个小时就爬到一两个有用的。
解决方案也不难,参考操作系统的调度算法不难发现,一级队列不行,我们就引入多级队列,从而实现优先级的区别,让优先级高的能够尽快被访问,至于优先级低的就排队去吧。
在这个程序中两级队列便足矣,访问过的无需分级,未访问的分为两个队列,每次出队列时,先从一级队列中出队,当一级队列中,才从二级队列中出队。
class MyQueue:
def __init__(self):
self.visited = []
self.unVisitedFirst = [] #采用多级队列实现优先级分级
self.unVisitedSecond = []
#将最后一个元素(即队列头)移出列表
def unVisitedDequeue(self):
try:
if len(self.unVisitedFirst) > 0: #只有当一级队列为空时,才返回二级队列的内容
return self.unVisitedFirst.pop()
else:
return self.unVisitedSecond.pop()
except:
return None
#将新加入的url插入到列表头(即对列尾),先进先出
def addUnvisitedUrl(self,url,level):
if url!='' :
if level == 1:
self.unVisitedFirst.insert(0,url)
else:
self.unVisitedSecond.insert(0,url)
取
爬解决了,接着就是取了,即通过url将页面的html抓取下来。这方面Python提供了一个很强大urllib库,使用也很方便,强烈不建议使用requests库,虽然使用超简单,但是速度真的慢的要死。。。要说的只有一点,要注意处理好各种异常,比如超时啊,超时啊,超时啊(重要的话说三遍!!!)
"""
抓取页面并进行解压处理
超时三次才将页面丢弃,防止丢弃过多页面
"""
def fetchPage(self,url):
page = ''
for i in range(3):
try:
socket.setdefaulttimeout(5)
req = request.Request(url,headers=self.headers)
page = request.urlopen(req).read()
page = self.gzdecode(page)
# print('success')
return page
except request.HTTPError as e:
print(e.reason)
return ''
except request.URLError as e:
print(e.reason)
return ''
except socket.timeout as e:
if i < 2:
continue
else:
print('timeout')
return ''
except:
print('other error')
return ''
过滤
分析。抓下来的网页含一堆的html标签,总不能就这样存吧,占空间不说,拿来做数据分析效率也低。所以我们还要进行过滤,简单粗暴也是最有效的方法就是正则表达式了,如此强大的工具用来过滤一下html自然是小case了。
这是参考网上不知道哪来的代码,我改改就拿来用了,如有雷同,纯属致敬。
"""
过滤HTML标签,合并多余空行
"""
def replace(self,content):
r = re.compile(r'<script.*?</script>',re.I|re.M|re.S) #删除JavaScript
s = r.sub ('',content)
r = re.compile(r'<style.*?</style>',re.I|re.M|re.S) #删除css
s = r.sub ('', s)
r = re.compile(r'<!--.*?-->', re.I|re.M|re.S) #删除注释
s = r.sub('',s)
r = re.compile(r'<meta.*?>', re.I|re.M|re.S) #删除meta
s = r.sub('',s)
r = re.compile(r'<ins.*?</ins>', re.I|re.M|re.S) #删除ins
s = r.sub('',s)
r = re.compile(r'<[^>]+>',re.M|re.S)
s = r.sub('',s).strip() #删除HTML标签
r = re.compile(r'^s+$', re.M|re.S) #删除空白行
s = r.sub ('', s)
r = re.compile(r'n+',re.M|re.S) #合并空行为一行
s = r.sub('n',s)
r = re.compile('s+',re.I|re.M|re.S)
s = r.sub('n',s)
return s
但是光是过滤这些还不够干净,我们真正感兴趣的是正文部分,而不包括网页四周那些边边角角的东西,所以下一步要做的就是把正文提取出来,为了做到这点,这里使用了一个名字很好听的算法:基于行块分布的正文提取算法。这个算法源于一篇论文《基于行块分布函数的通用网页正文抽取》,参考论文再加上一点思想写了下面这个算法。
"""
@param: values 正文段列表
k 每次选取的段数
@return:left 正文起始段
right 正文结束段
"""
def findEnd(values,k=5):
left,right = 0,0
# print(values)
for i in range(len(values)):
strCnt = sum(values[i:i+k])
# print(strCnt)
if strCnt > 180 and values[i] > 15:
if left == 0:
left = i
right = i
cnt = 0
for i in range(right,len(values)):
if cnt >= k:
break
if values[i] > 15:
right += 1
cnt += 1
return left,right
"""
找出正文的起始位置和结束位置
@param: content 过滤标签后的网页
k 每段选取的行数
@return:left 正文起始段
right 正文结束段
"""
def filt(content,k=1):
if not content:
return None,None
lines = content.split('n')
group_value = []
for i in range(0,len(lines),k):
group = 'n'.join(lines[i:i+k]).strip()
group_value.append(len(group))
left,right = findEnd (group_value)
return left,right
"""
关键的思路大概就这些了,有兴趣的话可以看看完整的代码,虽然写的略莽了些。
完整代码传送门
最后
以上就是爱笑服饰为你收集整理的一个用Python实现的多入口全网爬的多线程爬虫的实现的全部内容,希望文章能够帮你解决一个用Python实现的多入口全网爬的多线程爬虫的实现所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复