首页 诗词 字典 板报 句子 名言 友答 励志 学校 网站地图
当前位置: 首页 > 教程频道 > 开发语言 > perl python >

python网络爬虫之-感受篇BeautifulSoup

2013-12-26 
python网络爬虫之---体验篇BeautifulSoupPython抓取网页方法,任务是批量下载网站上的文件。对于一个刚刚入

python网络爬虫之---体验篇BeautifulSoup

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

?

1、用Python抓取网页

?

[python]?view plaincopyprint?
  1. import?urllib2,urllib??????
  2. url?=?'http://www.baidu.com'????
  3. req?=?urllib2.Request(url)????
  4. content?=?urllib2.urlopen(req).read()??

?

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

2)、content为网页的html源码

?

问题:

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

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

[python]?view plaincopyprint?
  1. import?urllib2,urllib????
  2. headers?=?{?#伪装为浏览器抓取????
  3. ????????'User-Agent':'Mozilla/5.0?(Windows;?U;?Windows?NT?6.1;?en-US;?rv:1.9.1.6)?Gecko/20091201?Firefox/3.5.6'????
  4. ????}????
  5. req?=?urllib2.Request(url,headers=headers)????
  6. content?=?urllib2.urlopen(req).read()??


更复杂的情况(需要登录,多线程抓取)可参考:http://www.pythonclub.org/python-network-application/observer-spider

?

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

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

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

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

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

[python]?view plaincopyprint?
  1. import?urllib,urllib2,bs4????
  2. req?=?urllib2.Request(url)????
  3. content?=?urllib2.urlopen(req).read()????
  4. content?=?bs4.BeautifulSoup(content)????
  5. return?content??


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

?

[python]?view plaincopyprint?
  1. 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??


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

?

2、用Python下载文件

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

[python]?view plaincopyprint?
  1. import?urllib????
  2. urllib.urlretrieve(url,?filepath)??


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

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

?

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

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

[python]?view plaincopyprint?
  1. import?re????
  2. content?=?''????
  3. match?=?re.compile(r'(?<=href=["]).*?(?=["])')????
  4. rawlv2?=?re.findall(match,content)??


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

?

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

或?http://wiki.ubuntu.org.cn/Python正则表达式操作指南

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

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

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

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

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

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

re.compile(r'.htm|.mid$')

?

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

?

3、使用BeautifulSoup分析网页

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

1、安装BS4

下载地址:http://www.crummy.com/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.crummy.com/software/BeautifulSoup/bs3/documentation.zh.html

1)、包含包:import bs4

2)、读入:

?

[python]?view plaincopyprint?
  1. req?=?urllib2.Request(url)????
  2. content?=?urllib2.urlopen(req).read()????
  3. content?=?bs4.BeautifulSoup(content,from_encoding='GB18030')???


3)、查找内容

?

?

a、按html标签名查找:

frameurl?=?content.findAll('frame')

framurl为存储所有frame标签内容的列表,例如frame[0] 为?tops.htm">

?

b、按标签属性查找

frameurl?=?content.findAll(target=True)

查找所有含target属性的标签

frameurl?=?content.findAll(target=‘m_rbottom’)

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

?

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

rawlv2?=?content.findAll(href=re.compile(r'.htm$'))

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

?

d、综合查找

frameurl?=?content.findAll('frame',target=‘rtop’)

?

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

?

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

a、访问标签属性值

[python]?view plaincopyprint?
  1. rawlv2?=?content.findAll(href=re.compile(r'.htm$'))????
  2. href?=?rawlv2[i]['href']??

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

b)、访问标签内容

[python]?view plaincopyprint?
  1. rawlv3?=?content.findAll(href=re.compile(r'.mid$'))????
  2. songname?=?str(rawlv3[i].text)??


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

?

?

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

[python]?view plaincopyprint?
  1. "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.dugukeji.com/'??
  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)#如果链接已保存且歌曲名长度比当前的长,略过??
  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. url?=?'http://www.dugukeji.com/'?
  174. req?=?urllib2.Request(url)?
  175. response?=?urllib2.urlopen(req).read()?
  176. response?=?unicode(response,'GBK').encode('UTF-8')?
  177. print?response?
  178. """??

热点排行