Scrapy抓取豆瓣相册(学习笔记)
情况是这样子的,因为前两天NHK的晨间剧《海女》完结了嘛,加之之前写《蜂蜜与四叶草》的时候说过到时要为海女专门写一篇的,于是乎,我下午就开始写啦,我写这种文章的时候总免不了去找图,然后在豆娘那里就看到了很多好图,尤其是能年犬的,所以就想把图片全部下下来,然后轮流当桌面,但是一看下面,狗眼瞎了,1500+张,于是,按照我的性格,果断就把《海女》的博文扔一边了,跑去研究怎么全部下下来好了。。。所以呢~海女的博文,我过几天再写吧。。。
前阵子一直在自学python,其实为了就是搞python(x,y)而已,而且没有搬砖需求,纯属自娱自乐。。我一开始就知道python很适合爬虫的,而且scrapy我“觊觎”很久了,准备学会python就狠狠搞一下!!今天是个机会,反正python学了好一部分了,所以就开始搞爬虫,几个小时下来,总算尼马把目的达成了!!
这里做一下笔记吧~反正今后会时不时发神经去网上“爬”一下的。。。。首先是安装scrapy,安装scrapy你需要准备好多好多东西,嗯,真的很麻烦。。
一,首先是python,还是比较推荐2.7吧,虽然我实验室的电脑不知道为什么2.7就是用不了。。。怎么安装请上网查吧,我是因为装了pythonxy,所以我就可以略过这一步了~
二,然后是安装Twisted【Twisted is an event-driven networking engine written in Python and licensed under the open source】,其中这个又分3步,所以说麻烦啊!!
- 安装setuptools:下载地址http://pypi.python.org/packages/2.7/s/setuptools/setuptools-0.6c11.win32-py2.7.exe
- 安装Zope.Interface:下载地址http://pypi.python.org/packages/2.7/z/zope.interface/zope.interface-4.0.1-py2.7-win32.egg,下载完成后将其拷贝到XXXPython27Scripts目录下,在cmd里面cd到这个目录后,运行:
easy_install.exe zope.interface-4.0.1-py2.7-win32.egg
- 安装Twisted:下载地址http://pypi.python.org/packages/2.7/T/Twisted/Twisted-12.1.0.win32-py2.7.msi,点击安装即可。
三,接下来是安装w3lib,下载地址http://pypi.python.org/packages/source/w/w3lib/w3lib-1.2.tar.gz,下载后解压,然后cmd进入到w3lib-1.2目录下,运行:
python setup.py install
四,下一步安装libxml2,下载地址http://users.skynet.be/sbi/libxml-python/binaries/libxml2-python-2.7.7.win32-py2.7.exe,神烦吧!点击安装即可。
五,别急。。下一步。。安装pyOpenSSL,下载地址http://pypi.python.org/packages/2.7/p/pyOpenSSL/pyOpenSSL-0.13.winxp32-py2.7.msi,还是点击安装。
六,最后一步,主角,Scrapy,下载地址http://pypi.python.org/packages/source/S/Scrapy/Scrapy-0.14.4.tar.gz,解压后cmd进入目录Scrapy-0.14.4,运行:
python setup.py install
好,战斗第一步,结束!
然后我下面简单说一下今天的学习笔记!
首先,我们需要建立一个scrapy的工程,所以在cmd下,cd到你喜欢的目录里面,运行
scrapy startproject doubanImage
这样子我们就多了一个叫做doubanImage的文件夹,下面就是我们的工程文件了,主要关心的文件有这两个:
- doubanImagedoubanImageitems.py:项目items文件
- doubanImagedoubanImagespiders:爬虫代码文件夹
其他的先别管,然后我们在spiders下面建立一个py文件,假设叫douban.py,写入代码如下:
from scrapy.spider import BaseSpider class DouBanImage(BaseSpider): name = "douban" allowed_domains = ["douban.com"] start_urls = [ "http://movie.douban.com/subject/10581289/photos?type=S" ] def parse(self, response): filename = 'hainv.txt' open(filename, 'wb').write(response.body)
然后在cmd里面运行,运行方式如下:scrapy crawl douban
第三个参数douban就是类里面的变量name,
代码里面,start_urls就是你要开始爬虫的其实网址,不一定只有一个。
parse是爬虫的回调函数,在这个代码里面,我们创建了一个叫做hainv.txt的文件夹,运行之后里面将会有这个地址页面的源代码,也就是你在浏览器那个页面下,右键→源文件,所看到的那些。
但是整个页面的代码其实并不是我们想要的,我们想要的是一个html树结构的那种东西,或者说,树结构也不是我们想要的,我们想要的是类似数据库一样,我告诉你我想要什么标签的信息,你就返回什么标签的信息;
还是《海女》相册页面http://movie.douban.com/subject/10581289/photos?type=S为例,我们查看源文件,可以看到我们感兴趣的照片都是这样一个标签下的:
<ul class="poster-col4 clearfix" id="" > <li data-id="2149092805"> <div class="cover"> <a href="http://movie.douban.com/photos/photo/2149092805/"> <img src="http://img3.douban.com/view/photo/thumb/public/p2149092805.jpg" /> </a> </div> <div class="prop"> 646x430 </div> <div class="name"> 最終週 <a href="http://movie.douban.com/photos/photo/2149092805/#comments">2回应</a> </div> </li> ........... </ul>
像http://movie.douban.com/photos/photo/2149092805/ 就是每张单独的照片的连接页面了,而http://img3.douban.com/view/photo/thumb/public/p2149092805.jpg 则是缩略图的照片的地址
于是乎,我们要找的照片其实就是ul下的li下的div下的a下面的img了!!要怎么得到这个东西呢?为了方便调试,一般不会直接在代码里面调试,我们可以开一个内建的一个Scrapy shell,这样我们就像运行matlab脚本一样随写随测试,方法呢,就是在cmd里面运行:
scrapy shell 地址
我们这里自然运行的是:
scrapy shell http://movie.douban.com/subject/10581289/photos?type=S
然后你就会发现进入了一个类似Ipython的界面了,要怎么获得我们刚刚上面描述的那个东西呢?输入:
hxs.select(‘//ul/li/div/a/@href’).extract()
其实就是告诉scrapy我要ul下面的li下面的div下面的a下面的链接,但是返回来是什么呢?
[u'http://movie.douban.com/photos/photo/2149092805/', u'http://movie.douban.com/photos/photo/2149092805/#comments', u'http://movie.douban.com/photos/photo/2151696753/', u'http://movie.douban.com/photos/photo/2151697922/', u'http://movie.douban.com/photos/photo/2151696816/', u'http://movie.douban.com/photos/photo/2151696808/', u'http://movie.douban.com/photos/photo/2151696793/', u'http://movie.douban.com/photos/photo/2151696793/#comments', u'http://movie.douban.com/photos/photo/2151696782/', u'http://movie.douban.com/photos/photo/2151696763/', u'http://movie.douban.com/photos/photo/2151696759/', u'http://movie.douban.com/photos/photo/2151696748/', u'http://movie.douban.com/photos/photo/2151696748/#comments', u'http://movie.douban.com/photos/photo/2151696740/', u'http://movie.douban.com/photos/photo/2149093162/', u'http://movie.douban.com/photos/photo/2149093162/#comments', u'http://movie.douban.com/photos/photo/2149093066/', u'http://movie.douban.com/photos/photo/2149093066/#comments', u'http://movie.douban.com/photos/photo/2149093000/', u'http://movie.douban.com/photos/photo/2149093000/#comments', u'http://movie.douban.com/photos/photo/2149092914/',...................
除了我们要的地址外,还有很多#comments,就是每张照片下面评论的链接啦~再看看我们上面的那个页面的源代码,可以发现,确实,我们刚刚输入的规则确实应该得到comment的连接,但是我们不想要这些?怎么办呢?观察一下上面,我们发现图片的地址的div里面class是‘cover’,而评论的class是‘name’,这就是区分的准则了!!代码如下:
hxs.select(“//ul/li/div[@class=’cover’]/a/@href”).extract()
这样返回来的全部都是图片页面的地址了,再也没有comments了。
如果想直接获得缩略图的链接呢?
hxs.select(“//ul/li/div[@class=’cover’]/a/img/@src”).extract()
这样返回来的就是:
[u'http://img3.douban.com/view/photo/thumb/public/p2149092805.jpg', u'http://img3.douban.com/view/photo/thumb/public/p2151696753.jpg', u'http://img3.douban.com/view/photo/thumb/public/p2151697922.jpg', u'http://img3.douban.com/view/photo/thumb/public/p2151696816.jpg', u'http://img4.douban.com/view/photo/thumb/public/p2151696808.jpg', u'http://img3.douban.com/view/photo/thumb/public/p2151696793.jpg', u'http://img3.douban.com/view/photo/thumb/public/p2151696782.jpg', 。。。。。。。。
接下来我原本的计划是,通过爬虫图片的页面地址,然后继续解析那个页面的源文件,找到图片的链接,但是我进去后就发现我太甜了,比如说这个页面,http://movie.douban.com/photos/photo/2151696782/,你进去就会发现没有直接的jpg的地址,那些图片的地址都是用JS函数计算出来的,虽然不算是加密,但是比较麻烦,但是在那个页面右键图片,属性,可以看到图片的URL,而且对比一下就会发现,如果缩略图的URL是:
http://img3.douban.com/view/photo/thumb/public/p2151696782.jpg
那么这个图片的URL就是
http://img3.douban.com/view/photo/photo/public/p2151696782.jpg
然后我们点击图片左下角的查看原图,就会发现,图片的链接是:
http://img3.douban.com/view/photo/raw/public/p2151696782.jpg
区别显而易见,所以为了图个方便,我们只要向上面一样解析出缩略图thumb的地址,就可以直接用一个replace函数获得原始图片的地址了,嗯,简单。
我们计划要把所有图片的地址写到douban.txt里面,然后直接贴到迅雷里面,就可以下载了,那么代码就变成这个了:
from scrapy.spider import BaseSpider from scrapy.selector import HtmlXPathSelector class DouBanImage(BaseSpider): name = "douban" allowed_domains = ["douban.com"] filename = 'douban.txt' f = open(filename, 'wb') start_urls = ["http://movie.douban.com/subject/10581289/photos?type=S"] def parse(self, response): hxs = HtmlXPathSelector(response) sites = hxs.select('//ul/li/div/a/img/@src').extract() for site in sites: site = site.replace('thumb','raw') self.f.write(site) self.f.write('rn')
这样我们就可以完成了第二步了!!
上面的步骤我们只是保存了第一页的图片,豆瓣设定一页40张,所以我们只得到了40张,要得到下面的怎么办呢?简单,获得下一页的地址不就行了咯~
继续看豆瓣海女相册第一页页面的源代码,在下面找到:
<div class="paginator"> <span class="prev"> <前页 </span> <span class="thispage">1</span> <a href="http://movie.douban.com/subject/10581289/photos?type=S&start=40&sortby=vote&size=a&subtype=a" >2</a> <a href="http://movie.douban.com/subject/10581289/photos?type=S&start=80&sortby=vote&size=a&subtype=a" >3</a> <a href="http://movie.douban.com/subject/10581289/photos?type=S&start=120&sortby=vote&size=a&subtype=a" >4</a> <a href="http://movie.douban.com/subject/10581289/photos?type=S&start=160&sortby=vote&size=a&subtype=a" >5</a> <a href="http://movie.douban.com/subject/10581289/photos?type=S&start=200&sortby=vote&size=a&subtype=a" >6</a>
很明显,从上面我们不仅可以很轻易的定位出我们要跳转的那一页的地址,还可以知道当前是第几页,每个地址对应第几页这些信息,比如:
hxs.select(“//div[@class=’paginator’]/a/@href”).extract()
上面这行语句就可以获得所有的页面的链接的跳转地址
hxs.select(“//div[@class=’paginator’]/a/text()”).extract()
上面这行语句就可以获得对应的页码。
虽然一开始思路是这样子,但是我后来想想还是算了,于是我就想到了另一个比较猥琐的方法。。。就是观察每一页的地址,可以发现。。。。很有规律性的。。。start=0,40,80,120…于是乎,一个简单无比的代码就出来了,见文章最后面。因为这里还有一个东西要说一下,就是Item那个文件。
某一个页面分析完,不一定像我这种只想要图片地址的,比如说可能有人还想要每张图片的评论,所以呢,就有了Item这个文件,对于编辑items.py这个文件,每添加一个项,就是用XXX=Filed()就可以了。
向我们这里,就是:
# Define here the models for your scraped items # # See documentation in: # http://doc.scrapy.org/topics/items.html from scrapy.item import Item, Field class DoubanimageItem(Item): # define the fields for your item here like: # name = Field() ImageAddress = Field() pass
这样,就可以对他进行保存了。
所以我最后的代码就是:
from scrapy.spider import BaseSpider from scrapy.selector import HtmlXPathSelector from doubanImage.items import DoubanimageItem class DouBanImage(BaseSpider): name = "douban" allowed_domains = ["douban.com"] start_urls = []; f = open('douban.txt', 'wb') for i in range(0,1560,40): start_urls.append('http://movie.douban.com/subject/10581289/photos?type=S&start=%d&sortby=vote&size=a&subtype=a'%i) def parse(self, response): hxs = HtmlXPathSelector(response) sites = hxs.select('//ul/li/div/a/img/@src').extract() items = [] for site in sites: site = site.replace('thumb','raw') self.f.write(site) self.f.write('\r\n') item = DoubanimageItem() item['ImageAddress'] = site items.append(item) return items
正所谓短小精悍,是吧~
但是跑完程序要怎么得到刚刚保存的item的值呢?运行的时候只要用以下参数即可:
scrapy crawl douban -o image.json -t json
这样运行完你就会看到一个image.json文件,东西就保存在那里面!!
题外话:
第一,上面那个东西只是我学了一下午得到的东西的整理,入门到不能再入门。
第二,话说,我发现用迅雷下载图片的时候,最好开单个文件下载,不然豆瓣那边会拒绝。
第三,我最后发现,我自己写一个现在图片的代码要比迅雷快很多,不知为何。。迅雷自己200张下了半个多小时,我1500+张十分钟不用就下完了。。估计豆瓣那边对迅雷这种多线程有封锁吧。。这里给一下读取图片链接下载图片的小代码。。也是scrapy的。。
from scrapy.spider import BaseSpider from scrapy.selector import HtmlXPathSelector class DownloadImg(BaseSpider): name = "readimg" allowed_domains = ["douban.com"] start_urls = []; f = open("F:\\readimg\\douban.txt",'r') line = f.readline() while(line): start_urls.append(line) line = f.readline() counter = 0; def parse(self, response): str = response.url[0:-3]; self.counter = self.counter+1 str = str.split('/'); print '--------------------------------DownLoad Finished',self.counter,str[-1] imgfile = open(str[-1],'wb') imgfile.write(response.body)
以上!【哎呀妈呀,总算写完了。。。海女的还是再等几天好了。。】
【完】
本文内容遵从CC版权协议,转载请注明出自http://www.kylen314.com
楼主,请问一下,如果是用CrawlSpider,Rule来爬多页的链接,一般主页的HTML中只有前面几页的URL,该怎么提取全部的URL啊?可能说的不清楚,假如海女的相册有60页,但是我们只能用xpath在start_urls给出的URL中select前面10页的URL,那后面的50页代码要怎么弄呢?(前提是不用你所说的猥琐的方法 – -!)
@akk
我这边回复邮件提示还没弄好,希望你会回头看我回复。。。
我不知道你其他页面是怎么个标记法。像海女这个页面,其实我是可以提取出当前页是第几页,一下下面每一页分别是第几页的,那么这样就可以逐页读取了
下面是我仿造你写的一个抓取手机论坛的讨论帖的爬虫,一共有63页,结果最后总是在抓第一页,只不过把第一页抓了63遍而已,还请博主给点指点。。。
from scrapy.spider import BaseSpider
from scrapy.selector import HtmlXPathSelector
from luntan.items import LuntanItem
class LuntanSpider(BaseSpider):
name=’luntan’
allow_domains=[‘bbs.angeeks.com’]
start_urls=[]
f=open(‘file.txt’,’wb’)
for i in range(1,64):
start_urls.append(‘http://bbs.angeeks.com/forum.php?mod=forumdisplay&fid=263&filter=typeid&typeid=12&page=%d’%i)
def parse(self,response):
hxs=HtmlXPathSelector(response)
items=[]
url=hxs.select(‘//tr/th/a/@href’).extract()
title=hxs.select(‘//tr/th/a/text()’).extract()
for url,title in zip(url,title):
item=LuntanItem()
self.f.write(url)
self.f.write(title.encode(‘gbk’)+’\r\n’)
item[‘url’]=url
item[‘title’]=title
items.append(item)
return items
from scrapy.spider import BaseSpider
from scrapy.selector import HtmlXPathSelector
from luntan.items import LuntanItem
class LuntanSpider(BaseSpider):
name=’luntan’
allow_domains=[‘bbs.angeeks.com’]
start_urls=[]
f=open(‘file.txt’,’wb’)
for i in range(1,64):
start_urls.append(‘http://bbs.angeeks.com/forum.php?mod=forumdisplay&fid=263&filter=typeid&typeid=12&page=%d’%i)
def parse(self,response):
hxs=HtmlXPathSelector(response)
items=[]
url=hxs.select(‘//tr/th/a/@href’).extract()
title=hxs.select(‘//tr/th/a/text()’).extract()
for url,title in zip(url,title):
item=LuntanItem()
self.f.write(url)
self.f.write(title.encode(‘gbk’)+’\r\n’)
item[‘url’]=url
item[‘title’]=title
items.append(item)
return items
这是我仿照你写的一个爬去手机论坛帖子的代码,一共有63页,可是为什么最后只爬到了第一页的帖子,只是爬了63遍而已,为什么呢?请博主给点指点
@akk
那个。。。如果你不急的话,我周末再帮你看一下吧。。。工作日有点忙。。
这个我已经解决了,是网站的问题,我换了个网站就没问题了。
我就想知道你说的那个逐页读取下一页是怎么实现的,要是方便的话还请帖下代码,新手不好意思了,呵呵~
@akk
一步一步爬的话参见以下http://www.kylen314.com/archives/1541,前面那部分。。我应该没理解错你的意思的话,你应该可以在这个的中间那段或者最后那段代码里面找到答案。。
(alpha)
楼主是把json内的链接贴到迅雷下载的?话说图片下载可以直接用Imagepipeline啊
本文的是好久之前写的了。。。现在一般用这个脚本:https://github.com/Vespa314/douban-image-scrapy
raw的图片爬不到噢,403错误,是不是得先登陆。