分享

Python抓取网页&批量下载文件方法初探(正则表达式+BeautifulSoup)

 当年剩女图书馆 2013-09-29

Python抓取网页&批量下载文件方法初探(正则表达式+BeautifulSoup)

分类: ubuntu python 数据挖掘 655人阅读 评论(0) 收藏 举报

最近两周都在学习Python抓取网页方法,任务是批量下载网站上的文件。对于一个刚刚入门python的人来说,在很多细节上都有需要注意的地方,以下就分享一下我在初学python过程中遇到的问题及解决方法。


一、用Python抓取网页

基本方法:

  1. <span style="font-size:14px;">import urllib2,urllib  
  2.   
  3. url = 'http://www.baidu.com'  
  4. req = urllib2.Request(url)  
  5. content = urllib2.urlopen(req).read()</span>  

1)、url为网址,需要加'http://'

2)、content为网页的html源码


问题:

1、网站禁止爬虫,不能抓取或者抓取一定数量后封ip

解决:伪装成浏览器进行抓取,加入headers:

  1. <span style="font-size:14px;">import urllib2,urllib  
  2.   
  3. headers = { #伪装为浏览器抓取  
  4.         'User-Agent':'Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US; rv:1.9.1.6) Gecko/20091201 Firefox/3.5.6'  
  5.     }  
  6.   
  7. req = urllib2.Request(url,headers=headers)  
  8. content = urllib2.urlopen(req).read()</span>  
  1. <span style="font-size:14px;">  
  2. </span>  
更复杂的情况(需要登录,多线程抓取)可参考:http://www./python-network-application/observer-spider,很不错的教程


2、抓取网页中的中文为乱码问题

解决:用BeautifulSoup解析网页(BeautifulSoup是Python的一个用于解析网页的插件,其安装及使用方法下文会单独讨论)

首先需要介绍一下网页中的中文编码方式,一般网页的编码会在<meta>标签中标出,目前有三种,分别是GB2312,GBK,GB18030,三种编码是兼容的,

从包含的中文字符个数比较:GB2312 < GBK < GB18030,因此如果网页标称的编码为GB2312,但是实际上用到了GBK或者GB18030的中文字符,那么编码工具就会解析错误,导致编码退回到最基本的windows-2152了。所以解决此类问题分两种情况。

1)、若网页的实际的中文编码和其标出的相符的话,即没有字符超出所标称的编码,下面即可解决

  1. <span style="font-size:14px;">import urllib,urllib2,bs4  
  2.       
  3. req = urllib2.Request(url)  
  4. content = urllib2.urlopen(req).read()  
  5. content = bs4.BeautifulSoup(content)  
  6. return content</span>  

2)、若网页中的中文字符超出所标称的编码时,需要在BeautifulSoup中传递参数from_encoding,设置为最大的编码字符集GB18030即可

  1. <span style="font-size:14px;">import urllib,urllib2,bs4  
  2.       
  3. req = urllib2.Request(url)  
  4. content = urllib2.urlopen(req).read()  
  5. content = bs4.BeautifulSoup(content,from_encoding='GB18030')  
  6. return content</span>  


详细的中文乱码问题分析参见:http://againinput4.blog.163.com/blog/static/1727994912011111011432810/

二、用Python下载文件

使用Python下载文件的方法有很多,在此只介绍最简单的一种

  1. <span style="font-size:14px;">import urllib  
  2.   
  3. urllib.urlretrieve(url, filepath)</span>  

url为下载链接,filepath即为存放的文件路径+文件名


更多Python下载文件方法参见:http:///code-snippet/83/sanzhong-Python-xiazai-url-save-file-code


三、使用正则表达式分析网页

将网页源码抓取下来后,就需要分析网页,过滤出要用到的字段信息,通常的方法是用正则表达式分析网页,一个例子如下:

  1. <span style="font-size:14px;">import re  
  2.   
  3. content = '<a href="http://www.baidu.com">'  
  4. match = re.compile(r'(?<=href=["]).*?(?=["])')  
  5. rawlv2 = re.findall(match,content)</span>  

用re.compile()编写匹配模板,用findall查找,查找content中所有与模式match相匹配的结果,返回一个列表,上式的正则表达式意思为匹配以‘href="'起始,以'"'结束的字段,使用非贪婪的规则,只取中间的部分

关于正则表达式,系统的学习请参见:http://www.cnblogs.com/huxi/archive/2010/07/04/1771073.html

或 http://wiki./Python%E6%AD%A3%E5%88%99%E8%A1%A8%E8%BE%BE%E5%BC%8F%E6%93%8D%E4%BD%9C%E6%8C%87%E5%8D%97

个人推荐第一篇,条理清晰,不重不漏

在此就不赘述正则表达式的学习,只总结一下我在实际写正则时的认为需要注意的几个问题:

1)、一定要使用非贪婪模式进行匹配,即*?,+?(后加?),因为Python默认使用贪婪模式进行匹配,例如'a.*b',它会匹配文档中从第一个a和最后一个b之间的文本,也就是说如果遇到一个b,它不会停止,会一直搜索至文档末尾,直到它确认找到的b是最后一个。而一般我们只想取某个字段的值,贪婪模式既不能返回正确的结果,还大大浪费了时间,所以非贪婪是必不可少的

2)、raw字符串的使用:如果要匹配一个.,*这种元字符,就需要加'\'进行转义,即要表示一个'\',正则表达式需要多加一个转义,写成'\\',但是Python字符串又需要对其转义,最终变成re.compile('\\\\'),这样就不易理解且很乱,使用raw字符串让正则表达式变得易读,即写成re.compile(r'\\'),另一个方法就是将字符放到字符集中,即[\],效果相同

3)、()特殊构造的使用:一般来说,()中的匹配模式作为分组并可以通过标号访问,但是有一些特殊构造为例外,它们适用的情况是:我想要匹配href="xxxx"这个模式,但是我只需要xxxx的内容,而不需要前后匹配的模式,这时就可以用特殊构造(?<=),和(?=)来匹配前后文,匹配后不返回()中的内容,刚才的例子便用到了这两个构造。

4)、逻辑符的使用:如果想匹配多个模式,使用'|'来实现,比如

  1. <span style="font-size:14px;">re.compile(r'.htm|.mid$')</span>  


匹配的就是以.htm或.mid结尾的模式,注意没有'&'逻辑运算符


四、使用BeautifulSoup分析网页

BeautifulSoup是Python的一个插件,用于解析HTML和XML,是替代正则表达式的利器,下文讲解BS4的安装过程和使用方法

1、安装BS4

下载地址:http://www./software/BeautifulSoup/#Download

下载 beautifulsoup4-4.1.3.tar.gz,解压:linux下 tar xvf beautifulsoup4-4.1.3.tar.gz,win7下直接解压即可

linux:

进入目录执行:

 1, python setup.py build 

 2, python setup.py install 

或者easy_install BeautifulSoup

win7:

cmd到控制台 -> 到安装目录 -> 执行上面两个语句即可


2、使用BeautifulSoup解析网页

本文只介绍一些常用功能,详细教程参见BeautifulSoup中文文档:http://www./software/BeautifulSoup/bs3/documentation.zh.html

1)、包含包:import bs4

2)、读入:

  1. <span style="font-size:14px;">req = urllib2.Request(url)  
  2. content = urllib2.urlopen(req).read()  
  3. content = bs4.BeautifulSoup(content,from_encoding='GB18030')  
  4. </span>  

3)、查找内容

a、按html标签名查找:

  1. <span style="font-size:14px;">frameurl = content.findAll('frame')</span>  

framurl为存储所有frame标签内容的列表,例如frame[0] 为 <framename="m_rtop" target="m_rbottom"src="tops.htm">

b、按标签属性查找

  1. <span style="font-size:14px;">frameurl = content.findAll(target=True)</span>  

查找所有含target属性的标签

  1. <span style="font-size:14px;">frameurl = content.findAll(target=‘m_rbottom’)</span>  

查找所有含target属性且值为'm_rbottom'的标签

c、带有正则表达式的查找

  1. <span style="font-size:14px;">rawlv2 = content.findAll(href=re.compile(r'.htm$'))</span>  

查找所有含href属性且值为以'.htm'结尾的标签

d、综合查找

  1. <span style="font-size:14px;">frameurl = content.findAll('frame',target=‘rtop’)</span>  

查找所有frame标签,且target属性值为'rtop'


4)、访问标签属性值和内容

a、访问标签属性值

  1. <span style="font-size:14px;">rawlv2 = content.findAll(href=re.compile(r'.htm$'))  
  2. href = rawlv2[i]['href']</span>  


通过[属性名]即可访问属性值,如上式返回的便是href属性的值


b)、访问标签内容

  1. <span style="font-size:14px;">rawlv3 = content.findAll(href=re.compile(r'.mid$'))  
  2. songname = str(rawlv3[i].text)</span>  

上式访问了<a href=...>(内容)</a>标签的实际内容,由于text为unicode类型,所以需要用str()做转换


附上最终的成果,程序功能是抓取www.上的所有midi文件并下载,需要先建立./midi/dugukeji/文件夹和./midi/linklist文件

  1. <span style="font-size:14px;">#-*- coding:utf-8 -*- #允许文档中有中文  
  2. import urllib2,urllib,cookielib,threading  
  3. import os  
  4. import re  
  5. import bs4  
  6. import sys  
  7. reload(sys)  
  8. sys.setdefaultencoding('utf-8'#允许打印unicode字符  
  9.   
  10.   
  11. indexurl = 'http://www./'  
  12. databasepath = './midi/linklist'  
  13. path = './midi/dugukeji/'  
  14. totalresult = {}  
  15. oriresult = {}  
  16.   
  17. def crawl(url):  
  18.     headers = { #伪装为浏览器抓取  
  19.         'User-Agent':'Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US; rv:1.9.1.6) Gecko/20091201 Firefox/3.5.6'  
  20.     }  
  21.     req = urllib2.Request(url,headers=headers)  
  22.     content = urllib2.urlopen(req).read()  
  23.     content = bs4.BeautifulSoup(content,from_encoding='GB18030')  
  24.     return content  
  25.   
  26.   
  27. def crawlframe(sourceurl,target):  
  28.     global indexurl  
  29.     content = crawl(sourceurl)  
  30.     #match = re.compile(r'(?<=target=["]'+target+'["] src=["]).*?(?=["])')   #正则表达式方法  
  31.     #frameurl = re.findall(match,content)  
  32.     frameurl = content.findAll('frame',target=target)   #beautifulsoup方法  
  33.     result = indexurl+frameurl[0]['src']  
  34.     return result  
  35.   
  36. def crawllv1(frameurl,st=-1,en=-1):  
  37.     global indexurl  
  38.     content = crawl(frameurl)  
  39.     #match = re.compile(r'(?<=href=["]).*?(?=["])')  
  40.     #rawlv2 = re.findall(match,content)  
  41.     rawlv2 = content.findAll(href=re.compile(r'.htm$'))  
  42.     result = []  
  43.     if st==-1 and en==-1:  
  44.         for i in range(len(rawlv2)):  
  45.             result.append(indexurl+rawlv2[i]['href'])  
  46.     else:  
  47.         for i in range(st,en):  
  48.             result.append(indexurl+rawlv2[i]['href'])  
  49.     #dele = []  
  50.     #for i in range(len(result)):  
  51.     #   if result[i][-4:]!='.htm' and result[i][-5:]!='.html':  
  52.     #       dele.append(i)  
  53. #       else:  
  54. #           result[i]=indexurl+result[i]  
  55. #   if len(dele)>0:  
  56. #       for deli in dele:  
  57. #           del result[deli]  
  58.   
  59.     #result.sort()  
  60.     return result  
  61.   
  62. def crawllv2(lv2url):  
  63.     global indexurl  
  64.     content = crawl(lv2url)  
  65.     #match = re.compile(r'(?<=href=["]\.\.\/).*?[">].*?(?=[<])')  
  66.     #rawlv3 = re.findall(match,content)  
  67.     rawlv3 = content.findAll(href=re.compile(r'[..].*?[0-9].htm|.mid$'))  
  68.     #print rawlv3  
  69.     result = {} #结果字典,key:链接,value:歌曲名  
  70.     for i in range(len(rawlv3)):  
  71.         tmp = str(rawlv3[i]['href'])  
  72.         #print tmp  
  73.         link = indexurl + tmp[tmp.rfind('..')+3:]   #有多个'..',找到最后一个  
  74.         songname = ''  
  75.         if tmp[-4:]=='.htm':    #需要访问3级页  
  76.             try:  
  77.                 conlv3 = crawl(link)  
  78.             except:  
  79.                 print 'WARNING: visit lv3 url failed!\n'  
  80.             else:  
  81.                 rawlv4 = conlv3.findAll(href=re.compile(r'.mid$'))  
  82.                 if not rawlv4:  #4级页没有.mid下载链接,略过  
  83.                     continue  
  84.                 else:  
  85.                     tmp = str(rawlv4[0]['href'])  
  86.                     link = indexurl + tmp[tmp.rfind('..')+3:]  
  87.   
  88.         songname = str(rawlv3[i].text)  #将unicode类型的text转化为string  
  89.         #songname.decode('GBK')  
  90.         #songname.encode('utf-8')  
  91.         songname = songname.replace(' ','_')    #将songname中空格和换行转化为下划线  
  92.         songname = songname.replace('\n','_')   #原来存在的链接,直接略过  
  93.         if oriresult.has_key(link):  
  94.             continue  
  95.         if totalresult.has_key(link) and len(songname)<len(totalresult[link]):   #如果链接已保存且歌曲名长度比当前的长,略过  
  96.             continue  
  97.         else:  
  98.             totalresult[link] = songname  
  99.             result[link] = songname     #加入字典  
  100.     #result.sort()  
  101.     return result  
  102.   
  103. def download(totalresult):  
  104.     for link in totalresult.keys():  
  105.         filepath = path + totalresult[link] + '.mid'  
  106.         print 'download: ',link,' -> ',filepath,'\n'  
  107.         urllib.urlretrieve(link, filepath)  
  108.   
  109.   
  110. def readdata(databasepath):  
  111.     datafile = open(databasepath,'r')   #读数据文件  
  112.     link = datafile.readline()  
  113.     while link:  
  114.         oriresult[link]=''  
  115.         link = datafile.readline()  
  116.     datafile.close()  
  117.   
  118. def writedata(databasepath):  
  119.     datafile = open(databasepath,'a')   #追加打开数据文件,将新链接写入文件尾  
  120.     for link in totalresult.keys():  
  121.         datafile.write(link,'\n')  
  122.     datafile.close()  
  123.   
  124. if __name__ == '__main__':  
  125.     try:  
  126.         readdata(databasepath)  #访问文件,记录已下载的链接  
  127.     except:  
  128.         print 'WARNING:read database file failed!\n'  
  129.     else:  
  130.         print 'There is ',len(oriresult),' links in database.\n'  
  131.   
  132.     try:  
  133.         frameurl1 = crawlframe(indexurl,'rtop'#抓取主页中一级页url所在frame的url  
  134.     except:  
  135.         print 'WARNING: crawl lv1 frameurl failed!\n'  
  136.     try:  
  137.         urllv1 = crawllv1(frameurl1,4,20)       #抓取一级页url  
  138.     except:  
  139.         print 'WARNING: crawl lv1 url failed!\n'  
  140.   
  141.     for i in urllv1:  
  142.         print 'lv1 url:',i  
  143.         try:  
  144.             frameurl2 = crawlframe(i,'rbottom'#抓取一级页中二级页url所在frame的url  
  145.         except:  
  146.             print 'WARNING: crawl lv2 frameurl failed!\n'  
  147.         else:  
  148.             print '\tlv2 frameurl:',frameurl2  
  149.             try:  
  150.                 urllv2 = crawllv1(frameurl2)    #抓取二级页url  
  151.             except:  
  152.                 print 'WARNING: crawl lv2 url failed!\n'  
  153.             else:  
  154.                 for j in urllv2:  
  155.                     print '\t\tlv2 url:',j  
  156.                     try:  
  157.                         urllv3 = crawllv2(j)  
  158.                     except:  
  159.                         print 'WARNING: crawl lv3 url failed!\n'  
  160.                     else:  
  161.                         for k in urllv3.keys():  
  162.                             print '\t\t\tlv3 url:',k,'\tname:',urllv3[k]  
  163.                         #download(urllv3)  
  164.                               
  165.     print 'new added midi num:',len(totalresult)  
  166.     print '\nbegin to download...\n'  
  167.     download(totalresult)  
  168.     print '\nWrite database...\n'  
  169.     writedata(databasepath)  
  170.     print '\n\nDone!\n'  
  171.   
  172.   
  173.   
  174.   
  175. """ 
  176. url = 'http://www./' 
  177. req = urllib2.Request(url) 
  178. response = urllib2.urlopen(req).read() 
  179. response = unicode(response,'GBK').encode('UTF-8') 
  180. print response 
  181. """</span>  


    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约