第一个爬虫 12月 31 python , 爬虫 字数统计: 2k(字) 阅读时长: 10(分)
2017 年最后一天,祝来年顺利 学了点 Python 后,总是想找点东西练下手,在知乎上一番了解之后,打算写一个爬虫。据说爬虫的初学者都要爬一次豆瓣电影 Top250。但我偏不。我选择豆瓣读书 Top250。
爬虫? 爬虫是一种按照一定的规则,自动地抓取万维网信息的程序或者脚本。——某度百科
自动抓取互联网信息?不明觉历,但了解过后其实并不是什么深奥的东西。大致可以分为三个步骤。
获取网页数据 分析网页的数据,拿到想要的信息 漂亮地输出 你需要了解 Python(其实是其他的不会)(Urllib,lxml,re) HTTP HTML 一点点 Xpath 一点点正则 获取网页数据 获取网页数据方法很多,特别是一些动态网页。爬豆瓣读书 Top250,是静态的。数据都在网页的源码里。所以只需要拿到 TML 源码就成功了。
拿到 URL 首先,我们要拿到 Url,也就是所谓的网址。https://book.douban.com/top250,第一个页面简单。怎么获取后面的几页呢? 第一个方法,分析源代码,只要获得了第一页的源码,接着就能拿到接下来几页的 Url 啦。 当然还有更简单的,观察网址发现 star= 后接的数字就是本页第一本书的排行。其实它并不一定是 25 的倍数,任何数字都可以。只是从 250 开始就不会有书了。 用 Python 可以这样'https://book.douban.com/top250'+'?start='+str(pn*25),让 pn 从 0,到 9 迭代,就可以啦。
拿到网页源码 有了 Url 要拿到网页源码只要借助 Python 的内建模块 urllib 就可以啦。 首先构造一个 Request,为了获得的源码尽可能和我看到的保持一致,除了 Url 还要加上 User-Agent。就可以轻松地获取网页源码啦。
1 2 3 4 5 6 7 8 9 10 from urllib import requestuser_agent='Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:57.0) Gecko/20100101 Firefox/57.0' header={'User-Agent' :user_agent} for pn in range (10 ): url='https://book.douban.com/top250' +'?start=' +str (pn*25 ) get=request.Request(url,headers=header) with request.urlopen(get) as r: content=(r.read().decode('utf-8' )) print (type (content))
分析数据 分析 HTML 用到了 Python 的 lXML 模块。他可以解析 string 类的 html 源码 为 DOM 树,还能通过 xPath 过去读取网页的元素。再加上点正则,就能拿到想要的数据啦。
这次我抓取书名、作者、译者、出版社、出版年月、售价、评分和简评。 看源码发现书本有关的数据都在一个 valign 属性为 top 的 td 标签中。(竟然是表格,怪不得教程都抓豆瓣电影)先提取出来。
1 for i in content.xpath('//tr[@class="item"]/td[@valign="top"]' ):
标题 1 2 3 4 5 6 7 8 9 10 11 12 13 def rm_empty (ls ): nl=[] for v in ls: v=v.strip() if len (v)>0 : nl.append(v) return nl for i in content.xpath('//tr[@class="item"]/td[@valign="top"]' ): title=rm_empty(i.xpath('div[@class="pl2"]/a/text()' )) if title !=None : pass
对象 Book 这样就拿到了标题,现在可以构造一个书本的类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 class Book (object ): """docstring for Book.""" def __init__ (self, title ): self.title = title self.tsl='None' self.comment='None' def __str__ (self ): if self.tsl =='None' : return " 书名: %s\n 作者: %s\n 出版社: %s\t 出版年月: %s\n 售价: %s\t 评分: %s\n 简评: %s" %(self.title, self.author, self.pub, self.date, self.price, self.rate, self.comment) else : return " 书名: %s\n 作者: %s\t 译者: %s\n 出版社: %s\t 出版年月: %s\n 售价: %s\t 评分: %s\n 简评: %s" %(self.title, self.author, self.tsl, self.pub, self.date, self.price, self.rate, self.comment)
Page 同样也可以构造一个 Page 类
1 2 3 4 5 6 7 8 9 10 class Page (object ): """docstring for Page.""" def __init__ (self,pn ): self.pn=pn user_agent='Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:57.0) Gecko/20100101 Firefox/57.0' self.header={'User-Agent' :user_agent} self.url = 'https://book.douban.com/top250' +'?start=' +str (pn*25 ) def get_request (self ): return (request.Request(self.url,headers=self.header))
Book 其他的属性 同样观察 HTML 源码,通过 xpath 获取,分别作为 book 对应的属性值
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 info=i.xpath('p[@class="pl"]/text()' ) info=re.split(r'\s+/\s+' ,info[0 ]) comment=i.xpath('p[@class="quote"]/span[@class="inq"]/text()' ) rate=i.xpath('div[@class="star clearfix"]/span[@class="rating_nums"]/text()' ) for j in range (len (info)-1 ): b=Book(title[0 ]) b.price=info[-1 ] b.date=info[-2 ] b.pub=info[-3 ] b.author=info[0 ] b.rate=rate[0 ] if len (comment)>0 : b.comment=comment[0 ] if len (info)==5 : b.tsl=info[-4 ]
漂亮地输出 对于每个 book 写好 book 类的 __str__ 函数
1 2 3 4 5 def __str__ (self ): if self.tsl =='None' : return " 书名: %s\n 作者: %s\n 出版社: %s\t 出版年月: %s\n 售价: %s\t 评分: %s\n 简评: %s" %(self.title, self.author, self.pub, self.date, self.price, self.rate, self.comment) else : return " 书名: %s\n 作者: %s\t 译者: %s\n 出版社: %s\t 出版年月: %s\n 售价: %s\t 评分: %s\n 简评: %s" %(self.title, self.author, self.tsl, self.pub, self.date, self.price, self.rate, self.comment)
然后增加点交互,和提示,就大功告成啦。
完整代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 from urllib import requestfrom lxml import htmlimport reurl_list=[] def rm_empty (ls ): nl=[] for v in ls: v=v.strip() if len (v)>0 : nl.append(v) return nl class Page (object ): """docstring for Page.""" def __init__ (self,pn ): self.pn=pn user_agent='Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:57.0) Gecko/20100101 Firefox/57.0' self.header={'User-Agent' :user_agent} self.url = 'https://book.douban.com/top250' +'?start=' +str (pn*25 ) def get_request (self ): return (request.Request(self.url,headers=self.header)) class Book (object ): """docstring for Book.""" def __init__ (self, title ): self.title = title self.tsl='None' self.comment='None' def __str__ (self ): if self.tsl =='None' : return " 书名: %s\n 作者: %s\n 出版社: %s\t 出版年月: %s\n 售价: %s\t 评分: %s\n 简评: %s" %(self.title, self.author, self.pub, self.date, self.price, self.rate, self.comment) else : return " 书名: %s\n 作者: %s\t 译者: %s\n 出版社: %s\t 出版年月: %s\n 售价: %s\t 评分: %s\n 简评: %s" %(self.title, self.author, self.tsl, self.pub, self.date, self.price, self.rate, self.comment) def start (choice,dir =None ): print (' 开始爬取 ' ) Book_list=[] for pn in range (10 ): print (' 正在爬取第 %d 页 ' %(pn+1 )) page=Page(pn) get=Page.get_request(page) with request.urlopen(get) as r: content=html.fromstring((r.read().decode('utf-8' ))) for i in content.xpath('//tr[@class="item"]/td[@valign="top"]' ): title=rm_empty(i.xpath('div[@class="pl2"]/a/text()' )) if title !=None : info=i.xpath('p[@class="pl"]/text()' ) info=re.split(r'\s+/\s+' ,info[0 ]) comment=i.xpath('p[@class="quote"]/span[@class="inq"]/text()' ) rate=i.xpath('div[@class="star clearfix"]/span[@class="rating_nums"]/text()' ) for j in range (len (info)-1 ): b=Book(title[0 ]) b.price=info[-1 ] b.date=info[-2 ] b.pub=info[-3 ] b.author=info[0 ] b.rate=rate[0 ] if len (comment)>0 : b.comment=comment[0 ] if len (info)==5 : b.tsl=info[-4 ] Book_list.append(b) print (' 爬取完成 ' ) if choice=='1' : dir =input (' 输入保存的目录 \n 如: /home/usrname/book.txt' ) with open (dir ,'w' ) as f: for book in Book_list: print (Book_list.index(book)+1 ,file=f) print (book,file=f) print ('++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++\n' ,file=f) f.close() elif choice =='2' : for book in Book_list: print (Book_list.index(book)+1 ) print (book) print ('++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++\n' ) choice=input ('[1]: 保存到文件 \n[2]: 直接输出 ' ) start(choice)
或许这就是所谓的「面条代码」吧。
运行效果
参考文章 Python 爬虫(3):爬取豆瓣电影 TOP250 瘳雪峰:urllib XPath 语法