python 爬虫实现前需要注意的地方

网络爬虫(又被称为网页蜘蛛,网络机器人,在FOAF社区中间,更经常的称为网页追逐者),是一种按照一定的规则,自动的抓取万维网信息的程序或者 脚本 。另外一些不常使用的名字还有蚂蚁,自动索引,模拟程序或者 蠕虫

参看百度百科

http://note.sdo.com/u/wyatt/n/mbnUS~jpc4spnM00k001a5


最近写网络爬虫多了,总结出一些心得,记录以备忘记.
网络爬虫的要求有几点:
一. 一个好的网页解释器,python推荐BeautifulSoup,java推荐htmlparser.
二.良好的性能,不能太慢,当遇上网络状况不大好的时候,慢更受不了,所以通常需要同时启动N个网络爬虫进行工作.使用队列可以很好解决这个问题.
三.稳定性,容错性.网络本来就存在不稳定性,连接超时,连接重置等是常见的不稳定现象,网络爬虫程序要充分妥当地处理这些问题,保证爬虫输出结果的完整性.
一.Beautiful Soup 美丽的汤 BeautifulSoup是一个纯py的html(xml)解释器,为许多python开发者所钟爱.其官方网站已有详尽的文档可作参考,再不然,google一下也大把中文文档.所以这里就不对其基本用法多加描述.仅对我走过的弯路进行一下小结.
1 <ul>的contents并不全是<li>,<head>的nextSibling并不一定是<body> 代码说话:
python 代码
   
   
   
   
  1. >>>from BeautifulSoup import BeautifulSoup  
  1. >>>soup = BeautifulSoup(‘<ul><li>abc</li><li>def</li></ul>’)  
  1. >>> ul = soup.find(‘ul’)  
  1. >>> ul.contents  
  1. [<li>abc</li>, <li>def</li>]  
上面的代码说明<ul>标签下面的子结点全是<li>,试过这小段HTML代码有回车也得到同样的结果,于是我想当然地认为从网 页上扒下来的HTML内容也会得到同样的结果..但实践很快证明并不是这样的,实际上很多网页制作过程中使用了一些不可见字符(如空格,制表符等),这些 字符并不会被忽略,而是被汤看作是NavigableString类型的元素. 所以当我想当然地for li in ul时,很快发现里面有很多杂质而导致错误.更好的方式是自己写个小函数,只取标签即可: python 代码
  1. def right_tag(tag,name=None):  
  1.     if not name or tag.name == name:  
  1.         return tag  
  1.   
  1. def tag_children(tag,name=None):  
  1.     """ 
  1.     找到所有类型为标签的子元素,可通过name参数指定标签种类. 
  1.     """  
  1.     children = []  
  1.     contents = tag.contents  
  1.     for content in contents:  
  1.         if isinstance(content,Tag):  
  1.             child = right_tag(content,name)  
  1.             if child:  
  1.                 children.append(child)  
  1.     return children  
同样的道理,nextSibling,preSibling也不一定稳妥地帮你找到相邻的标签,下面的函数可以帮你: python 代码
  1. def next_tag(tag,name=None):  
  1.     """ 
  1.     找到下一个邻近的标签,可指定标签名 
  1.     """  
  1.     current = tag.nextSibling  
  1.     while current:  
  1.         if isinstance(current,Tag):  
  1.             return right_tag(current,name)  
  1.         current = current.nextSibling  
2 编码问题 先复习一下Python的编码机制. python的内部编码为unicode,如果需要将字符串从编码A转为编码B时,顺序为: 编码A —(decode)—>内部编码 —(encode)—>编码B BeautifulSoup在解释网页文件的时候会把文字转换成unicode的.既然是这样,那应该不用担心编码问题了.友人网的网页是GB2312编 码,在不用指定特殊编码的情况下可以获取到正常的中文,而在我扒当当网的时候,问题出现了,同样是GB2312的编码,但扒下来存在文件的却是乱码.(其 实友人网也有部分乱码,但大部分正常).我检查控制台的输出时发现,BeautifulSoup并没有把中文转换成为真正的双字节unicode,或许说 转换得并不成功,它仅仅是在GB2312的编码串前面加了一个u,如 u’\xe4\xb8\xad\xe6\x96\x87′ ,把原本是GB编码的代码假装成了unicode,所以在处理的时候成了乱码. Beautiful Soup 的文档说:会按顺序尝试不同的编码将你的文档转换为Unicode: * 可以通过fromEncoding参数传递编码类型给soup的构造器 * 通过文档本身找到编码类型:例如XML的声明或者HTML文档http-equiv的META标签。如果Beautiful Soup在文档中发现编码类型,它试着使用找到的类型转换文档。但是,如果你明显的指定一个编码类型,并且成功使用了编码:这时它会忽略任何它在文档中发 现的编码类型。 * 通过嗅探文件开头的一下数据,判断编码。如果编码类型可以被检测到,它将是这些中的一个:UTF-*编码,EBCDIC或者ASCII。 * 通过chardet 库,嗅探编码,如果你安装了这个库。 * UTF-8 * Windows-1252 我在构造BeautifulSoup对象的时候并没有指定编码,那么看来是第二点起作用了,但效果并不理想,所以最好的方式还是在构造器指定编码. 如soup = BeautifulSoup(page,fromEncoding=’gb2312′) 实践证时,在构造器指定编码是最佳实践. 二.优化性能 使有队列辅助多线程提速 假如使用单线程去运行网络爬虫,那速度一定让人受不了,由于网络的延时随时可以把一条线程阻塞. 多线程可以充分利用带宽和计算机资源,给网络爬虫提速. IBM文档中心这篇<<使用python正行线程编程>>正好使用网络爬虫来做为示例讲述如何进行多线程编程以及如何使用队列来优化多线程编码的.很值得参考. 三.容错处理 网络爬虫最常见的错误莫过于连接超时和连接被重置了.而这些错误又是偶然性的,就是说,同一个网页,第一次可能连接超时,第二次可能就正常了.这要求在程 序的设计时考虑到如何这些错误的连接,我的做法是把出问题的连接记录下来,等大队列工作完成后再循环重试有问题的连接,直到全部正常. 另外,关于网络超时的问题.如果超时时间过长的话,网速慢的时候会大大影响网络爬虫的执行速度,所以通常有人希望设置网络连接的超时 值.python2.5的urlib2的urlopen函数并没有接受timeout参数,(尽管python2.6提供了,目前没敢用).不过可以用 socket.setdefaulttimeout()来设定.当然,可以到pythoncn参考一些行者给出的建议.




http://blog.csdn.net/fbd2011/article/details/7208194

Python实现网络爬虫

分类: python   934人阅读  评论(0)  收藏  举报
本人刚学Python不久,前段时间看到华南师范大学有一个网络爬虫的项目,特意研究了一下,用Python写了一个可以多线程下载的网络爬虫。

其实网络爬虫的原理并不是太复杂,具体的一些东西可以参照百度百科的描述,在这里我就不再多说。

我使用的网络爬虫下载网页的算法是广度搜索(BFS),网络上对爬虫实现算法的评价中,广度搜索的算法是排行第二的,最好的算法是按网页重要性排序后再确定下载顺序(这个算法很灵活,怎么排序本人不是很了解)。

进入正题,描述如何实现:
    拿到一个已经有了描述的办法,实现它可以按自顶向下的思路,先将大的步骤描述出来,然后分割成小的问题,一部分一部分地解决。
    对于一个网络爬虫,如果要按广度遍历的方式下载,它就是这样干活的:
        1.从给定的入口网址把第一个网页下载下来
        2.从第一个网页中提取出所有新的网页地址,放入下载列表中
        3.按下载列表中的地址,下载所有新的网页
        4.从所有新的网页中找出没有下载过的网页地址,更新下载列表
        5.重复3、4两步,直到更新后的下载列表为空表时停止
    其实就是简化成下面的步骤:
        1.按下载列表进行下载
        2.更新下载列表
        3.循环操作1,2,直到列表为空结束

    所以最初的设想就是写一个函数里面干这个:
def craw():
    while len(urlList) != 0
        Init_url_list()
        Download_list()
        Update_list()
     当然,上面这个函数是工作不起来的,它只是最顶层的一个想法,底层的实现还没做。不过这一步很重要,至少让自己知道该干什么了。
    下面的事情就是将函数每一部分实现,这个可以放在一个类里去实现,我把它命名为WebCrawler。
     在python里,要按一个地址下载一个网页那并不是什么难事,你可以用urllib里的urlopen去连接上某一个网页,然后调用获取到的对象的read方法,可以得到网页的内容的字符串,像这样:
IDLE 2.6.6      ==== No Subprocess ====
>>> import urllib
>>> f = urllib.urlopen('http://www.hfut.edu.cn')
>>> s = f.read()
>>> 

     这样上面变量 s 里面存的就是从http://www.hfut.edu.cn这个地址里获取到的网页的内容了,是str数据类型。下面你要怎么用都可以了,把写入文件或从中提取新的地址就随你意了。当然,只要写入文件,就算下载完了这个页面。
     一个爬虫程序下载的速度肯定是很重要的问题,谁也不想用一个单线程的爬虫用一次只下一个网页速度去下载,我在学校校园网,测试了单线程的爬虫,平均每秒才下1k。所以解决的办法只有用多线程,多开几个连接同时下载就快了。本人是Python新手,东西都是临时拿来用的。
     下载线程我是用了另外一个类,命名为CrawlerThread,它继承了threading.Thread这个类
     因为涉及到更新下载列表的问题,线程对某个表的读写还要考虑同步,我在代码里使用了线程锁,这个用threading.Lock()构造对象。调用对象的acquire()和release()保证每次只有一个线程对表进行操作。当然,为了保证表的更新能够实现,我使用了多个表,一个表肯定办不成。因为你即要知道当前要下载的网络地址,还要知道你已经下载过的网络地址。你要把已经下载过的地址从新的网页中获取到的网址列表中除去,这当中又涉及了一些临时的表。
     爬虫在下载网页的时候,最好还要把哪个网页存到了哪个文件记录好,并且记录好网页是搜索到广度搜索到的第几层的深度记录好,因为如果要做搜索引擎,这个都是对制作索引和对网页排序有参考价值的信息。至少你自己会想知道爬虫给你下载到了什么,都放在哪了吧。对应的写记录的语句我在代码里的行末用##标注出来了。
     写的文字已经很多了,不想再写了,直接贴上代码:

文件Test.py内容如下:(它调用了WebCrawler,运行时是运行它)
-------------------------------------------------------
-
# -*- coding: cp936 -*-
import WebCrawler

url = raw_input('设置入口url(例-->http://www.baidu.com): \n')
thNumber = int(raw_input('设置线程数:'))    #之前类型未转换出bug

wc = WebCrawler.WebCrawler(thNumber)
wc.Craw(url)


文件WebCrawler.py内容如下:
--------------------------------------------------------

# -*- coding: cp936 -*-
import threading
import GetUrl
import urllib

g_mutex = threading.Lock()
g_pages = []      #线程下载页面后,将页面内容添加到这个list中
g_dledUrl = []    #所有下载过的url
g_toDlUrl = []    #当前要下载的url
g_failedUrl = []  #下载失败的url
g_totalcount = 0  #下载过的页面数

class WebCrawler:
    def __init__(self,threadNumber):
        self.threadNumber = threadNumber
        self.threadPool = []
        self.logfile = file('#log.txt','w')                                   ##

    def download(self, url, fileName):
        Cth = CrawlerThread(url, fileName)
        self.threadPool.append(Cth)
        Cth.start()

    def downloadAll(self):
        global g_toDlUrl
        global g_totalcount
        i = 0
        while i < len(g_toDlUrl):
            j = 0
            while j < self.threadNumber and i + j < len(g_toDlUrl):
                g_totalcount += 1    #进入循环则下载页面数加1
                self.download(g_toDlUrl[i+j],str(g_totalcount)+'.htm')
                print 'Thread started:',i+j,'--File number = ',g_totalcount
                j += 1
            i += j
            for th in self.threadPool:
                th.join(30)     #等待线程结束,30秒超时
            self.threadPool = []    #清空线程池
        g_toDlUrl = []    #清空列表

    def updateToDl(self):
        global g_toDlUrl
        global g_dledUrl
        newUrlList = []
        for s in g_pages:
            newUrlList += GetUrl.GetUrl(s)   #######GetUrl要具体实现
        g_toDlUrl = list(set(newUrlList) - set(g_dledUrl))    #提示unhashable
                
    def Craw(self,entryUrl):    #这是一个深度搜索,到g_toDlUrl为空时结束
        g_toDlUrl.append(entryUrl)
        depth = 0
        while len(g_toDlUrl) != 0:
            depth += 1
            print 'Searching depth ',depth,'...\n\n'
            self.downloadAll()
            self.updateToDl()
            content = '\n>>>Depth ' + str(depth)+':\n'                         ##(该标记表示此语句用于写文件记录)
            self.logfile.write(content)                                        ##
            i = 0                                                              ##
            while i < len(g_toDlUrl):                                          ##
                content = str(g_totalcount + i) + '->' + g_toDlUrl[i] + '\n'   ##
                self.logfile.write(content)                                    ##
                i += 1                                                         ##
         
class CrawlerThread(threading.Thread):
    def __init__(self, url, fileName):
        threading.Thread.__init__(self)
        self.url = url    #本线程下载的url
        self.fileName = fileName

    def run(self):    #线程工作-->下载html页面
        global g_mutex
        global g_failedUrl
        global g_dledUrl
        try:
            f = urllib.urlopen(self.url)
            s = f.read()
            fout = file(self.fileName, 'w')
            fout.write(s)
            fout.close()
        except:
            g_mutex.acquire()    #线程锁-->锁上
            g_dledUrl.append(self.url)
            g_failedUrl.append(self.url)
            g_mutex.release()    #线程锁-->释放
            print 'Failed downloading and saving',self.url
            return None    #记着返回!
        
        g_mutex.acquire()    #线程锁-->锁上
        g_pages.append(s)
        g_dledUrl.append(self.url)
        g_mutex.release()    #线程锁-->释放

文件GetUrl.py内容如下:(它里面的GetUrl从一个存有网页内容的字符串中获取所有url并以一个list返回,这部分实现方法很多,大家可以自己写个更好的)
--------------------------------------------------------

urlSep = ['<','>','\\','(',')', r'"', ' ', '\t', '\n']
urlTag = ['http://']

def is_sep(ch):
    for c in urlSep:
        if c == ch:
            return True
    return False

def find_first_sep(i,s):
    while i < len(s):
        if is_sep(s[i]):
            return i
        i+=1
    return len(s)

def GetUrl(strPage):
    rtList = []
    for tag in urlTag:
        i = 0
        i = strPage.find(tag, i, len(strPage))
        while i != -1:
            begin = i
            end = find_first_sep(begin+len(tag),strPage)
            rtList.append(strPage[begin:end])
            i = strPage.find(tag, end, len(strPage))

    return rtList

你可能感兴趣的:(多线程,python,网络,python,url,文档,网络爬虫)