Python核心编程-网络爬虫的分析

# -*-coding:utf-8 -*-
'''
Created on 2014年7月5日


@author: root


网络爬虫


'''


from sys import argv
from os import makedirs,unlink,sep
from os.path import dirname,exists,isdir,splitext
from string import replace,find,lower
from htmllib import HTMLParser
from urllib import urlretrieve
from urlparse import urlparse,urljoin
from formatter import DumbWriter,AbstractFormatter
from cStringIO import StringIO


class Retriever(object): #download web page
    def __init__(self, url):
        self.url = url  #浏览url
        self.file = self.filename(url) #本地保存路径
    
    #根据url确定本地存放路径
    def filename(self, url, deffile='index.htm'):
        #urlparse : Return a 6-tuple: (scheme, netloc, path, params, query, fragment).
        parsedurl = urlparse(url, 'http:', 0) #parse path

 
 
 

'''

urlparse.urlparse  分析url,将url分解为各部分    

Return a 6-tuple: (scheme, netloc, path, params, query, fragment).                 

详情:http://jingyan.baidu.com/article/a17d52852323928098c8f2fc.html

'''

from urlparse import urlparse

url = 'http://user:pass@NetLoc:80/path;parameters?query=argument#fragment'

parsedurl = urlparse(url)print parsedurl

#ParseResult(scheme='http', netloc='user:pass@NetLoc:80', path='/path', params='parameters', query='query=argument', fragment='fragment')

print '服务器位置(也可能有用户信息):',parsedurl[1]#服务器位置(也可能有用户信息): user:pass@NetLoc:80

print '网页文件在服务器中存放的位置:',parsedurl[2]#网页文件在服务器中存放的位置: /path

print '服务器位置(也可能有用户信息):',parsedurl.netloc#服务器位置(也可能有用户信息): user:pass@NetLoc:80

print '网页文件在服务器中存放的位置:',parsedurl.path#网页文件在服务器中存放的位置: /pathprint '用户名:',parsedurl.username#用户名: user

print '密码:',parsedurl.password#密码: pass

        path = parsedurl[1] + parsedurl[2] # netloc+path
        ext = splitext(path) 
        #os.path.splitext(filename) 
        #把文件名分成文件名称和扩展名 
        #os.path.splitext(abc/abcd.txt) 
        #('abc/abcd', '.txt')
'''
os.pah.splitext
Split the extension from a pathname.将后缀名从路径中分离出去
    
    Extension is everything from the last dot to the end, ignoring
    leading dots.  Returns "(root, ext)"; ext may be empty 后缀名可能为空
'''
from os.path import splitext
path = 'c:\\a\\b\\c\\test.txt'
root,ext = splitext(path)
print root  #c:\a\b\c\test
print ext   #.txt  不为空,所以是个文件,如果为空,则path是个纯路径


path = 'c:\\a\\b\\c'
root,ext = splitext(path)
print root  #c:\a\b\c
print ext   #  为空,则path是个纯路径
        if ext[1] == '': #后缀名为空,则说明是纯目录,不是文件
            if path[-1] == '/':
                path +=deffile
            else:
                path +='/' + deffile
        #如果要下载网页,此时path=www.xxx.com/a/b/c.htm|c.ext,必须先创建目录ldir = www.xxx.com/a/b 
        ldir = dirname(path) #local direcotry  
        #dirname('d:/pythonSrc/test/test.py')    
        #d:/pythonSrc/test
'''
os.path.dirname
dirname(p)
    Returns the directory component of a pathname
             返回包含路径名的目录
'''
from os.path import dirname
print dirname('http://www.codesky.net/article/201002/123013.html')
#http://www.codesky.net/article/201002
print dirname('c:\\a\\b\\c')  #c:\a\b
print dirname('c:\\a\\b\\c\\') #c:\a\b\c
#为什么c:\a\b\c 和 c:\a\b\c\ 上面会有这样的不同,看了下源代码明白了return split(p)[0]
from os.path import split
print '1',split('c:\\a\\b\\c')[0]   #1 c:\a\b
print '2',split('c:\\a\\b\\c\\')[0] #2 c:\a\b\c
print dirname(r'c:\a\b\c\test.txt')  #c:\a\b\c
        if sep != '/':      #os.sep路径分隔符		
            ldir = replace(ldir,'/',sep)
'''
sep
首先,Python 是跨平台的。
在 Windows 上,文件的路径分割符号是 '\' ,在 Linux 上 是 ‘/’。
为了让你的代码在不同的平台上都能运行,那么你写路径的时候是写 ‘/’ 还是写 '\' 呢?
使用 os.sep 的话,你就不用去考虑这个了,os.sep 根据你所处的平台,自动地采用相应的分割符号。
举例:
Linux下一个路径,  /usr/share/python,那么上面的 os.sep 就是 ‘/’
Windows下一个路径, C:\Users\Public\Desktop, 那么上面的 os.sep 就是 '\'。


'''
from os import sep
print sep #\

        if not isdir(ldir) :
            if exists(ldir):
                #只有当ldir是一个文件路径时,才进入,但ldir=dirname()过了,还能是个文件?
'''
isdir
    Return true if the pathname refers to an existing directory.
            如果该路径是已存在的目录,返回真
            注意只能判断目录是否存在,不能判断文件是否存在
isexist 不仅能判断该目录是否存在,还能判断该文件是否存在
'''
from os.path import isdir,exists


print isdir('c:\\a\\b') #False
print exists('c:\\a\\b') #False
print isdir('c:\\') #True
print exists('c:\\') #True
print isdir(r'C:\Program Files\Windows Defender\MSASCui.exe')   #False
print exists(r'C:\Program Files\Windows Defender\MSASCui.exe')  #True

                print '这句不会被执行吧?0000000000000000000000000'
                unlink(ldir) #删除文件,不能删除目录</span>
'''
unlink(...)
    unlink(path)
    
    Remove a file (same as remove(path)).
            删除一个文件(与remove相同)
'''
from os import unlink
#unlink('d:\\test1') #报错,只能删除文件
#unlink('d:\\test1\test.txt') 


from os import rmdir
#rmdir('d:\\test1') #test1文件夹必须为空才能被删除
#rmdir('d:\\test2\\test') #test文件夹必须为空才能被删除
#rmdir('d:\\test2') #因为d:\\test2 还有目录 \test ,所以报错。即test2不为空,不能删除

from shutil import rmtree
#rmtree('d:\\test2')  #删除目录树

            makedirs(ldir) #创建文件的本地目录    www.xx.com/a/b
'''
mkdir(...)
    mkdir(path [, mode=0777])
    
    Create a directory.
             创建一个目录
'''
from os import mkdir
#print mkdir('d:\\test1') #None   在D盘创建test1文件夹,如果存在test1,报错
#mkdir('d:\\test2\\test') #必须存在D:\test2   否则报错

'''
makedirs(name, mode=511)
    makedirs(path [, mode=0777])
    
    Super-mkdir; create a leaf directory and all intermediate ones.
    Works like mkdir, except that any intermediate path segment (not
    just the rightmost) will be created if it does not exist.  This is
    recursive.
          创建递归的目录树,可以是相对或者绝对路径 
          如果子目录创建失败或者已经存在,会抛出一个OSError的异常,Windows上Error 183即为目录已经存在的异常错误。如果path只有一级,与mkdir一样。

'''
from os import makedirs
#makedirs('d:\\test2\\test')   #即使没有D:\test2,该目录也能创建

        return path    #返回文件存放本地路径 www.xx.com/a/b/c.html        
    
    
    #下载页面,返回下载反馈信息
    def download(self):  #download Web
        try:
            print 'self.file=',self.file
            print 'self.url=',self.url
            retval = urlretrieve(self.url, self.file)

'''
urlretrieve    方法直接将远程数据下载到本地
urlretrieve(url, filename=None, reporthook=None, data=None)


filename指定保存到本地的路径(若未指定该,urllib生成一个临时文件保存数据)
reporthook回调函数,当连接上服务器、以及相应的数据块传输完毕的时候会触发该回调
data指post到服务器的数据


'''
from urllib import urlretrieve


def callback_f(download_size, block_size, remote_total_size):
    per = 100 * download_size * block_size / remote_total_size
    if per>100:
        per = 100
    #print '%.2f%'%per
    print 'Already download %d KB(%.2f'%(download_size*block_size/1024,per)+'%)'
url = 'http://cywl.jb51.net:81/201209/books/pythonxx_jb51.rar'
localfilepath=r'.\download.rar'
urlretrieve(url, localfilepath,callback_f)

            print retval    #('www.sjzyz.net/index.htm', <httplib.HTTPMessage instance at 0x0181E580>)
        except IOError:
            retval = ('*** ERROR:invalid URL "%s"'%self.url,)    
        return retval
    
    #分析下载好的网页内容,返回href列表
    def parseAndGetLinks(self): #parse HTML, save links
        #使用HTMLParser的方法进行处理,StringIO是从内存中读取数据,DumbWriter将事件流转换为纯文本文档。
        self.parser = HTMLParser(AbstractFormatter(DumbWriter(StringIO())))

'''
HTMLParser
是python用来解析html的模块。它可以分析出html里面的标签、数据等等,是一种处理html的简便途径


详情:http://www.linuxqq.net/archives/710.html
'''
from htmllib import HTMLParser
from formatter import DumbWriter,AbstractFormatter
parser = HTMLParser(AbstractFormatter(DumbWriter()))
content = '''<html><body><p>I'm a paragraph!</p><img src='http://www.google.com/intl/zh-CN_ALL/images/logo.gif' /><a href = 'http://www.163.com'/><a href="http://www.163.com" mce_href="http://www.163.com">链接到163</a><a href = '/test/'/><body></html'''
parser.feed(content)
print '*'*50
#The list of hyperlinks is available as the data attribute anchorlist.     
#href='' 的表单
print parser.anchorlist #['http://www.163.com', 'http://www.163.com', '/test/']
print '*'*50

		
        self.parser.feed(open(self.file).read()) #进行解析
        self.parser.close()
        return self.parser.anchorlist
        
class Crawler(object):#manage entire crawling process
    count=0 #static downloaded page counter

    def __init__(self,url):
        self.q=[url]      #待浏览URL栈  
        self.seen=[]      #已经浏览过的网页
        self.dom=urlparse(url)[1] #netloc
    #增加新的链接到栈中
    def getPage(self,url):
        r=Retriever(url)
        retval=r.download()
        print 'retval=',retval
        if retval[0]=='*': #error situation,do not parse
            print retval,'...skipping parse'
            return
        Crawler.count+=1
        print '\n(',Crawler.count,')'
        print 'URL:',url
        print 'FILE:',retval[0]
        self.seen.append(url)

        links=r.parseAndGetLinks() #get and process links
        #print 'links:',links
        for eachLink in links:
            #print 'eachLink:',eachLink
            if eachLink[:4]!='http' and find(eachLink,'://')==-1:  #如果链接没有http://开头。例如/News/
                eachLink=urljoin(url,eachLink)
'''
urljoin
'''
from urlparse import urljoin
base = 'http://www.baidu.com/article/test.html'
url = '/article/test2.html'
print urljoin(base,url) #http://www.baidu.com/article/test2.html
url2 = '/article2/test2.html'
print urljoin(base,url2) #http://www.baidu.com/article2/test2.html


#如下实现urljoin
print urlparse(base)[0]+'://'+urlparse(base)[1]+url2 #http://www.baidu.com/article2/test2.htm

            print '* ',eachLink,

            if find(lower(eachLink),'mailto:') !=-1:  #如果在链接中找到mailto:,抛弃
                print '... discarded,mailto link'
                continue

            if eachLink not in self.seen:   #未浏览过的连接
                if find(eachLink,self.dom)==-1:
                    print '...discarded,not in domain' #抛弃外网链接
                else:
                    if eachLink not in self.q:
                        self.q.append(eachLink)         #增加新的连接
                        print '...new,added to Q'           
                    else:
                        print '...discarded,already processed'    #抛弃已经浏览过的链接
            else:
                print '...discarded,already processed'
    #弹出一个链接进行getpage
    def go(self):#process links in queue
        while self.q: #待浏览栈不为空
            url=self.q.pop() #弹出末尾,所以是栈
'''
q.pop
'''


q = ['1','2','3']
print q.pop() #3
print q #['1', '2']

            self.getPage(url)

def main():
    #如果已经在一个命令行给出URL(例如我们直接调用这个脚本程序的时候),它就会从给定的URL起开始运行;
    if len(argv)>1:
        url=argv[1]

    else:
        try:
            url=raw_input('Enter starting URL:')
        except (KeyboardInterrupt,EOFError):
            url=''
        if not url:return
        robot=Crawler(url)
        robot.go()

if __name__=='__main__':
    main()  #http://www.csdn.net/报错  因为程序认为http://www.csdn.net/和http://www.csdn.net是不同的。所以应该修改下

书上源程序直接拿来如果用 http://www.csdn.net/ 就会报错

所以我在原有基础改了下

# -*- coding:utf-8 -*-
'''
Created on 2014年7月7日

@author: root

'''
from urlparse import urlparse, urljoin
from urllib import urlretrieve
from os.path import isdir,sep,dirname,splitext
from os import makedirs
from string import replace,lower,find
from htmllib import HTMLParser
from formatter import AbstractFormatter, DumbWriter
from StringIO import StringIO
class Retrieve(object):
    def __init__(self, url):
        self.url = url
        self.filename = self.filename(url)
    def filename(self, url, deffext = 'index.htm'): #url=http://www.xxx.com/a/b/c/|d.html
        parse = urlparse(url)
        path = parse[1]+parse[2]
        
        #如果path是个纯目录
        ext = splitext(path)
        if ext[1] == '': #后缀名为空,则说明是纯目录,不是文件
            if path[-1] == '/':
                path +=deffext
            else:
                path +='/' + deffext
        
        
        if sep !='/':
            path = replace(path, '/', sep)
        ldir = dirname(path)
        if not isdir(ldir): #如果不存在该目录是一个目录或是个文件(dirname后,ldri不能是文件)
            makedirs(ldir)
        return path
    def download(self):
        def downcall(down_count, block_size, total_size):
            per = 100 * down_count * block_size / total_size
            if per>100:
                per = 100
            print '正在下载 %d KB(%.2f'%(down_count*block_size/1024,per)+'%)'
            
        try:
            content = urlretrieve(self.url,self.filename,downcall)
        except IOError:
            content = '**Error'
        finally:
            return content
        
    def getHrefs(self):
        parser = HTMLParser(AbstractFormatter(DumbWriter(StringIO())))
        parser.feed(open(self.filename).read())
        parser.close()
        return parser.anchorlist
class Crawl(object):
    count =0
    def __init__(self,url):
        self.q=[url]
        self.seen=[]
        self.dom = urlparse(url)[1]
    def getPage(self,url):
        r = Retrieve(url)
        self.seen.append(url)
        retval = r.download()
        Crawl.count +=1
        if retval[0] =='*':
            print '%s下载错误,跳过...'%url
            return
        print '(%d) %s下载成功!'%(Crawl.count,url)
        print '*开始分析 URL %s'%url
        anlist = r.getHrefs()
        for eachAn in anlist:
            eachAn = lower(eachAn)
            print 'eachAn:',eachAn
            if eachAn[0:4]!='http' and find(eachAn,'://')==-1:
                eachAn = urljoin(url,eachAn)
                
            #源程序无此判断
            if eachAn[-1]!='/':
                eachAn+='/'
            
            if find(eachAn,'mailto:') !=-1:  #如果在链接中找到mailto:,抛弃
                print '        邮件链接,跳过...'
                continue
            if eachAn not in self.seen:
                if find(eachAn,self.dom)==-1:
                    print '        发现其他域名,跳过...'
                else:
                    if eachAn not in self.q:
                        print '        可用,添加到栈中等待分析...'
                        self.q.append(eachAn)
                    else:
                        print '        已经在栈中等待分析,跳过...'
            else:
                print '        已经分析过了,跳过...'
                                        
        print '*分析URL %s结束...'%url
    def go(self):
        while self.q:
            url = self.q.pop()
            self.getPage(url)
        print '**********全部结束**********'
        
        
def main():
    url = raw_input('请输入出发url:')
    print '**********开始**********'
    robot = Crawl(url)
    robot.go()
                    
                
if __name__ == '__main__':
    main()

一个简单的Python网络爬虫大概如此,如果我改的那里不对了,请留言告诉我,我是菜鸟。。。 微笑 奋斗

还很多功能有待完善,比如权值的判断,哪些页面要优先访问,采用线程等。







  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值