首页 > Python > Scrapy抓取豆瓣相册(学习笔记)

Scrapy抓取豆瓣相册(学习笔记)

2013年10月1日 发表评论 阅读评论

情况是这样子的,因为前两天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步,所以说麻烦啊!!

  1. 安装setuptools:下载地址http://pypi.python.org/packages/2.7/s/setuptools/setuptools-0.6c11.win32-py2.7.exe
  2.  安装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

  3. 安装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">
            &lt;前页
        </span>

                <span class="thispage">1</span>

            <a href="http://movie.douban.com/subject/10581289/photos?type=S&amp;start=40&amp;sortby=vote&amp;size=a&amp;subtype=a" >2</a>

            <a href="http://movie.douban.com/subject/10581289/photos?type=S&amp;start=80&amp;sortby=vote&amp;size=a&amp;subtype=a" >3</a>

            <a href="http://movie.douban.com/subject/10581289/photos?type=S&amp;start=120&amp;sortby=vote&amp;size=a&amp;subtype=a" >4</a>

            <a href="http://movie.douban.com/subject/10581289/photos?type=S&amp;start=160&amp;sortby=vote&amp;size=a&amp;subtype=a" >5</a>

            <a href="http://movie.douban.com/subject/10581289/photos?type=S&amp;start=200&amp;sortby=vote&amp;size=a&amp;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

分类: Python 标签: , ,
  1. akk
    2013年12月11日18:02 | #1

    楼主,请问一下,如果是用CrawlSpider,Rule来爬多页的链接,一般主页的HTML中只有前面几页的URL,该怎么提取全部的URL啊?可能说的不清楚,假如海女的相册有60页,但是我们只能用xpath在start_urls给出的URL中select前面10页的URL,那后面的50页代码要怎么弄呢?(前提是不用你所说的猥琐的方法 – -!)

  2. 2013年12月11日21:31 | #2

    @akk
    我这边回复邮件提示还没弄好,希望你会回头看我回复。。。

    我不知道你其他页面是怎么个标记法。像海女这个页面,其实我是可以提取出当前页是第几页,一下下面每一页分别是第几页的,那么这样就可以逐页读取了

  3. akk
    2013年12月12日14:18 | #3

    下面是我仿造你写的一个抓取手机论坛的讨论帖的爬虫,一共有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

  4. akk
    2013年12月12日14:20 | #4

    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遍而已,为什么呢?请博主给点指点

  5. 2013年12月12日16:13 | #5

    @akk
    那个。。。如果你不急的话,我周末再帮你看一下吧。。。工作日有点忙。。

  6. akk
    2013年12月12日21:31 | #6

    这个我已经解决了,是网站的问题,我换了个网站就没问题了。
    我就想知道你说的那个逐页读取下一页是怎么实现的,要是方便的话还请帖下代码,新手不好意思了,呵呵~

  7. 2013年12月12日22:08 | #7

    @akk
    一步一步爬的话参见以下http://www.kylen314.com/archives/1541,前面那部分。。我应该没理解错你的意思的话,你应该可以在这个的中间那段或者最后那段代码里面找到答案。。

  8. test
    2013年12月17日18:59 | #8

    (alpha)

  9. 2014年10月24日11:14 | #9

    楼主是把json内的链接贴到迅雷下载的?话说图片下载可以直接用Imagepipeline啊

    • 2014年10月24日12:31 | #10

      本文的是好久之前写的了。。。现在一般用这个脚本:https://github.com/Vespa314/douban-image-scrapy

  10. 2016年4月4日10:04 | #11

    raw的图片爬不到噢,403错误,是不是得先登陆。

验证码:5 + 7 = ?

友情提示:留言可以使用大部分html标签和属性;

添加代码示例:[code lang="cpp"]your code...[/code]

添加公式请用Latex代码,前后分别添加两个$$