设为首页 收藏本站
查看: 1795|回复: 0

[经验分享] 利用python爬取海量疾病名称百度搜索词条目数的爬虫实现

[复制链接]

尚未签到

发表于 2015-11-30 14:46:34 | 显示全部楼层 |阅读模式
  实验原因:
  目前有一个医疗百科检索项目,该项目中对关键词进行检索后,返回的结果很多,可惜结果的排序很不好,影响用户体验。简单来说,搜索出来的所有符合疾病中,有可能是最不常见的疾病是排在第一个的,而最有可能的疾病可能需要翻很多页才能找到。
  
  实验目的:
  为了优化对搜索结果的排序,想到了利用百度搜索后有显示搜索到多少词条,利用这个词条数,可以有效的对疾病排名进行一个优化。从一方面看,某一个疾病在百度的搜索词条数目越多,表示这个词条的信息特别丰富,侧面反映了搜索这个词条的人特别多,从而可以推出这个疾病在人群中可能是发生概率较高的。反过来看,如果一个疾病很罕见,人们只有很低的概率会患这种疾病,那么相应的搜索这个词条的人也就会少,相应的网页也就少,因此搜索引擎搜索出来的词条数目也就会少。
  
  实验过程:
  
  第一阶段:从数据库中获取疾病名称
    这一阶段涉及到如何利用python从数据库中提取数据,我利用的是MySQLdb库,通过利用以下代码建立连接提取数据:
  



db = MySQLdb.connect('localhost', 'root', '', 'medical_app', charset = 'utf8')
cu = db.cursor()
cu.execute('select * from table order by id')
  
  其中在connect函数中我设置了charset属性,这是因为如不这样做python读取出来的数据库中文信息会变成乱码。
  
  在这一阶段,我编写了一个dbmanager类,主要负责数据库的读取和插入工作,刚才介绍的已经可以完成一个读取任务了,那么怎么利用python对数据进行添加呢?



cu.execute('insert into table(id,name) value(?, ?)',[a,b])
  
  第二阶段:完成数据的爬取
  最初我尝试了利用python的urllib来对百度网页进行爬取,发现这条路是走不通的,百度发现是机器爬取后会返回错误的网页代码。
  于是就想到了模拟浏览器的方式来对百度网页进行互动,爬取网页的内容。找到了mechanize库,表示这个库非常好用,使用非常简单。除了能够模仿浏览器读取网页以外,还可以和网页进行交互操作,然后还可以设置机器人选项,通过这个选项可以读取屏蔽机器人的网页,比如说百度。



br = mechanize.Browser()
br.open("http://www.example.com/")
# follow second link with element text matching regular expression
response1 = br.follow_link(text_regex=r"cheese\s*shop", nr=1)
assert br.viewing_html()
print br.title()
print response1.geturl()
print response1.info()  # headers
print response1.read()  # body

br.select_form(name="order")
# Browser passes through unknown attributes (including methods)
# to the selected HTMLForm.
br["cheeses"] = ["mozzarella", "caerphilly"]  # (the method here is __setitem__)
# Submit current form.  Browser calls .close() on the current response on
# navigation, so this closes response1
response2 = br.submit()
  以上是简单的mechanize的使用说明。以下是官网对mechanize的简单说明,不得不说,这个配合HTML解析器用起来太方便了。



  •   mechanize.Browser and mechanize.UserAgentBase implement the interface of urllib2.OpenerDirector, so:

    •   any URL can be opened, not just http:

    •   mechanize.UserAgentBase offers easy dynamic configuration of user-agent features like protocol, cookie, redirection and robots.txt handling, without having to make a new OpenerDirector each time, e.g. by callingbuild_opener().



  •   Easy HTML form filling.

  •   Convenient link parsing and following.

  •   Browser history (.back() and .reload() methods).

  •   The Referer HTTP header is added properly (optional).

  •   Automatic observance of robots.txt.

  •   Automatic handling of HTTP-Equiv and Refresh.


  关于BeautifulSoup,这是一个在数据挖掘领域非常好用的HTML解析器,他将网页解析成有标签所构成的树形字典结构,想要找到网页中的某一元素用find函数非常轻松的就可以找到。
  



html = urllib.open('http://mypage.com')
soup = BeautifulSoup(html.read())
soup.find('div', {'class':'nums'})
  上面这句话的意思是,找到网页中class属性为nums的div标签内容。
  
  第三阶段:异常的捕获和超时设置
  爬取网页内容的爬虫已经写好,拿出来跑一跑,发现百度经常性的会返回一些错误的网页导致网页无法正确填充表格或者分析,这个时候我们需要抓取这里面的异常让程序能够持续运行,让抓取出现异常的疾病名称放回队列稍后再进行爬取。关于异常捕获,代码如下:
  



1 try:
2    br = mechanize.Browser()
3    br.set_handle_robots(False)
4    br.open(URL)
5    br.select_form('f')
6    br['wd'] = name[1].encode('utf8')
7    response = br.submit()
8    #print 'form submitted, waiting result...'
9    #分析网页,有可能百度返回错误页面
10    soup = BeautifulSoup(response.read())
11    # text = soup.find('div', {'class':'nums'}).getText()
12    if soup.find('div', {'class':'nums'}):
13       text = soup.find('div', {'class':'nums'}).getText()
14    else:
15       print '$Return page error,collect again...'
16       self.manual.push_record(name)
17       continue
18             except socket.timeout:
19                 print '$there is an error occur.it will check later...'
20                 self.manual.push_record(name)
21                 print name[1],' pushed into the list.'
22                 continue
  这里可以看到为了提高检索效率,设置了一个超时异常,利用socket组件中的timeout。在这段代码之前我们需要设置下超时时间



1 import socket
2
3 socket.setdefaulttimeout(5)
4
5 try:
6     ...
7 except socket.timeout :
8     print 'timeout'
  这段示例代码设置的是5秒钟的超时时间。
  
  到目前为止,利用这些知识,我已经完成了一个在爬取过程中不会出错的一个单线程爬虫模块,显然这个爬虫爬取内容的效率是非常慢的。我决定用多线程来让它快起来。
  
  第四阶段:多线程的爬虫控制
  这一阶段,我们需要设计一个可以进行多线程网页爬取的爬虫设计。这里面我们主要考虑两点:1,如何实现多线程;2,关于公共变量的同步读取问题如何实现。
  对于第一个问题,在python中多线程的实现有两种方式:
  第一种是函数式:

  函数式:调用thread模块中的start_new_thread()函数来产生新线程。语法如下:

thread.start_new_thread ( function, args[, kwargs] )

  参数说明:


  • function - 线程函数。
  • args - 传递给线程函数的参数,他必须是个tuple类型。
  • kwargs - 可选参数。
  

  第二种是线程模块:

  Python通过两个标准库thread和threading提供对线程的支持。thread提供了低级别的、原始的线程以及一个简单的锁。
  thread 模块提供的其他方法:


  • threading.currentThread(): 返回当前的线程变量。
  • threading.enumerate(): 返回一个包含正在运行的线程的list。正在运行指线程启动后、结束前,不包括启动前和终止后的线程。
  • threading.activeCount(): 返回正在运行的线程数量,与len(threading.enumerate())有相同的结果。
  除了使用方法外,线程模块同样提供了Thread类来处理线程,Thread类提供了以下方法:


  • run(): 用以表示线程活动的方法。
  • start():启动线程活动。
  • join([time]): 等待至线程中止。这阻塞调用线程直至线程的join() 方法被调用中止-正常退出或者抛出未处理的异常-或者是可选的超时发生。
  • isAlive(): 返回线程是否活动的。
  • getName(): 返回线程名。
  • setName(): 设置线程名。

  之前尝试了利用第一种方式来实现函数,发现这样实现出来的代码结构很不清晰,在变量同步的时候回会显得非常混乱。于是采用了第二种方法来实现这个函数:



class myThread (threading.Thread):   #继承父类threading.Thread
def __init__(self, threadID, name, Manual):
threading.Thread.__init__(self)
self.lock = thread.allocate_lock()
self.threadID = threadID
self.name = name
self.manual = Manual
def run(self):                   #把要执行的代码写到run函数里面 线程在创建后会直接运行run函数
print "Starting " + self.name
self.get_rank()
print "Exiting " + self.name
def get_rank(self): #爬虫代码,持续获取疾病得分
        ...
...
  如何实现线程的运作呢?代码如下:



for i in xrange(thread_count): #建立线程
mythread = myThread(i,'thread-'+str(i),m)
thread_queue.append(mythread)
for i in xrange(thread_count): #启动线程
    thread_queue.start()
for i in xrange(thread_count): #结束线程
thread_queue.join()
  现在我们已经可以通过利用线程来对疾病进行爬取了,可是在对爬取结果怎么进行同步存储,怎么对疾病名称进行同步读取呢?
  
  第四阶段:变量的同步操作
  这一阶段我们需要设计好如何才能够可行的对同步变量进行操作,这一方面是比较烧脑的。。。设计如下:
DSC0000.png
  其中B类是线程类,A类是同步变量控制类,A类的主要功能是提供变量V1,V2的同步操作,包括读取写入之类的。B类是线程类,负责爬取网页的数据。
  B类在上面已经说过了,A类的实现如下:



class Manual: #同步变量控制
def __init__(self, names):
self.names = names
self.results = []
self.lock = threading.RLock()
def get_name(self): #获得疾病名称
        self.lock.acquire()
if len(self.names):
name = self.names.pop()
#print 'name get'
            self.lock.release()
return name
else:
self.lock.release()
return None
def put_result(self, result): #存放得分
        self.lock.acquire()
self.results.append(result)
print '(%d/6811)' %len(self.results)
self.lock.release()
def push_record(self, name): #放回获取失败的疾病名
        self.lock.acquire()
self.names.append(name)
self.lock.release()
  
  最后,所有部分都已实现,就差组装起来跑动啦。目前在实验室苦逼的跑啊跑中。。网不给力,拿了4个线程跑,保守目测得4个小时。
  
  【原创-Blaxon】

运维网声明 1、欢迎大家加入本站运维交流群:群②:261659950 群⑤:202807635 群⑦870801961 群⑧679858003
2、本站所有主题由该帖子作者发表,该帖子作者与运维网享有帖子相关版权
3、所有作品的著作权均归原作者享有,请您和我们一样尊重他人的著作权等合法权益。如果您对作品感到满意,请购买正版
4、禁止制作、复制、发布和传播具有反动、淫秽、色情、暴力、凶杀等内容的信息,一经发现立即删除。若您因此触犯法律,一切后果自负,我们对此不承担任何责任
5、所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其内容的准确性、可靠性、正当性、安全性、合法性等负责,亦不承担任何法律责任
6、所有作品仅供您个人学习、研究或欣赏,不得用于商业或者其他用途,否则,一切后果均由您自己承担,我们对此不承担任何法律责任
7、如涉及侵犯版权等问题,请您及时通知我们,我们将立即采取措施予以解决
8、联系人Email:admin@iyunv.com 网址:www.yunweiku.com

所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其承担任何法律责任,如涉及侵犯版权等问题,请您及时通知我们,我们将立即处理,联系人Email:kefu@iyunv.com,QQ:1061981298 本贴地址:https://www.yunweiku.com/thread-145475-1-1.html 上篇帖子: 使用python拼接多张图片.二三事 下篇帖子: python之supervisord启动脚本
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

扫码加入运维网微信交流群X

扫码加入运维网微信交流群

扫描二维码加入运维网微信交流群,最新一手资源尽在官方微信交流群!快快加入我们吧...

扫描微信二维码查看详情

客服E-mail:kefu@iyunv.com 客服QQ:1061981298


QQ群⑦:运维网交流群⑦ QQ群⑧:运维网交流群⑧ k8s群:运维网kubernetes交流群


提醒:禁止发布任何违反国家法律、法规的言论与图片等内容;本站内容均来自个人观点与网络等信息,非本站认同之观点.


本站大部分资源是网友从网上搜集分享而来,其版权均归原作者及其网站所有,我们尊重他人的合法权益,如有内容侵犯您的合法权益,请及时与我们联系进行核实删除!



合作伙伴: 青云cloud

快速回复 返回顶部 返回列表