Python实现网络爬虫

本人刚学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
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值