Scrapy抓取多层网页结构详解(三)

作者: chengcxy | 来源:发表于2017-04-08 00:47 被阅读792次

    昨晚在群里看到一朋友问用scrapy框架抓取妹子图网站,抓到的总是一个页面,清明期间理顺了数据存储关系就写下练手,在用scrapy之前我用单独的py脚本测试了一下,没大问题就往框架上套用,有许多细节还是需要关心的,稍后会总结一下稍不留神就犯的错,下一篇文章将对比框架和单独py脚本的实现逻辑。

    一、大致画一个爬取流程图

    爬取流程
    进入http://www.meizitu.com/a/list_1_1.html
    这个页面会有封面图,封面图的url类似http://www.meizitu.com/a/5517.html
    跳转到封面图页面后再抓这个页面的图片。
    类似于入口url一样的url有92个,也就是到list_1_92.html,假如第一页有30个封面图,每个封面图有10张图,最后抓取到的详情页的图片应该是92乘30乘10=27600张图片(*替换乘号居然不显示,封面图不计算在内),因此我们会在spider中定义两个函数,一个parse函数 从入口url解析出封面url,对封面url进行回调parse_page函数获取详情页的图片数据并接收parse函数获取到的items.py里定义的需要获取的数据,同时parse函数里包含一个除了入口url外的url列表,对其循环回调自身parse函数实现所有url的遍历和跨页面的数据获取,因此我存储的数据结构也是按照这个逻辑,去代码实现
    数据存储结构:可以看到我是按网站进入的层级层层存储,字段顺序是一对多的关系,从左到右代表入口页的url可以有多个封面图,而封面图又可以有多张图片 数据存储结构

    二、具体代码实现(跟随想存储的数据结构写逻辑)

    1、items.py

    from scrapy import Item,Field
    
    class MeizituItem(Item):
       list_url=Field()
       fengmian_url=Field()
       image_url=Field()
       image_name=Field()
    

    2、MeizituSpider.py

    #coding:utf-8
    import sys
    sys.path.append('..')
    
    from scrapy.http import Request
    from scrapy.selector import Selector
    from scrapy.spiders import CrawlSpider
    from items import MeizituItem
    
    class MeiziSpider(CrawlSpider):
        name = 'meizituespider'
        start_urls = ['http://www.meizitu.com/a/list_1_1.html']
        allowed_domains = ["meizitu.com"]
        def parse(self,response):
            #空列表用来添加下面对封面url列表循环时候当前请求的url和当前页面解析出的每个封面url
            items=[]
            print response.url
            selector=Selector(response)
            infos = selector.xpath('//li[@class="wp-item"]//h3[@class="tit"]')
            for info in infos:
                #item实例化 数据类型字典 在循环里实例化 最后将字典添加进items列表
                item=MeizituItem()
                fengmian_url=info.xpath('a/@href')[0].extract()
                print fengmian_url
                item['list_url']=response.url
                item['fengmian_url'] = fengmian_url
                items.append(item)
           #此时所有的封面url和请求的url数据都是一个字典中的value,字典作为一个元素添加进列表
           #items列表此时不再空,有多少封面url就有多少个item字典元素
           #若此时入口url的值如果为a,封面url为(b1,b2,b3,b4),那么items数据为
           #tems=[
               # {'list_url':'a','fengmian_url':‘b1’},
               # {'list_url':'a','fengmian_url':'b2'},
               #{'list_url':'a','fengmian_url':'b3'},
               #{'list_url':'a','fengmian_url':'b4'}
            #
           #这样写代码,是和我设定的数据存储目标一致的,见上面数据储存结构 
           #如果不存这层数据 items空列表是没有必要建立的,在上面循环里可以直接回调parse_page函数了。
            for item in items:
                #对列表解析 列表的第一个item现在是item={'list_url':'a','fengmian_url':‘b1’}
                #此时就可以回调parse_page函数,利用meta参数将第一层的入口url传递给下一层了,meta是一个字典
                yield Request(url=item['fengmian_url'],meta={'item_1': item}, callback=self.parse_page)
            #入口url为http://www.meizitu.com/a/list_1_1.html,这样的页面有92个因此我们用一个循环,遍历生成新的url回调parse这个自身函数解析,就依次进入第2到92个url的每个封面url,获取每个封面url的所有图片了
            for i in range(2,93):
                req_url='http://www.meizitu.com/a/list_1_%s.html' % i
                yield Request(url=req_url,callback=self.parse)
    
        def parse_page(self,response):
             #进入封面url的页面 首先接收传递出来的数据 items为字典
             #items={'list_url':'a','fengmian_url':‘b1’} 此时response.url为b1
             items = response.meta['item_1']
             selector=Selector(response)
             infos = selector.xpath('//div[@class="postContent"]//p/img')
             print len(infos)
             #对b1封面url页面的所有图片进行循环遍历
             for info in infos:
                 #实例化一个MeizituItem()对象 这个才是最后提交到piplines处理的
                 item=MeizituItem()           
                 image_url = info.xpath('@src')[0].extract()
                 image_name = info.xpath('@alt')[0].extract()
                 #添加图片数据入item
                 item['image_url'] = image_url
                 item['image_name'] = image_name
                 #这里因为for 循环获取的是每个图片的信息 根据我们最终的数据储存结构必须包含 list_url :a 封面url:b1
                 #因此下面代码是将接收的值复制给item的这两个key :'list_url'和 'fengmian_url' 。至此 items,py里定义的4个Field字段都在一个item里
                 #一个item是一个图片的信息字典 也对应着数据表中的一行记录
                 item['list_url']=items['list_url']
                 item['fengmian_url']=items['fengmian_url']
                 print item
                 yield item
    

    3、piplines.py

    import MySQLdb
    def dbHandle():
        conn = MySQLdb.connect( 
            host = "localhost", 
            user = "root", 
            passwd = "密码",
            charset = "utf8",
            db='local_db',
            port=3306)  
        return conn  
    
    class MeizituPipeline(object):
        def process_item(self, item, spider):
            dbObject = dbHandle()
            cursor = dbObject.cursor()
            sql = "insert into meizitu values (%s,%s,%s,%s)"
            try:
                cursor.execute(sql, (item['list_url'], item['fengmian_url'],  item['image_url'], item['image_name']))
                cursor.connection.commit()
            except BaseException as e:
                print("存储错误", e, "<<<<<<原因在这里")
                dbObject.rollback()
            return item
    

    4、settings.py

    BOT_NAME = 'meizitu'
    SPIDER_MODULES = ['meizitu.spiders']
    NEWSPIDER_MODULE = 'meizitu.spiders'
    USER_AGENT = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_3) AppleWebKit/536.5 (KHTML, like Gecko) Chrome/19.0.1084.54 Safari/536.5'
    ROBOTSTXT_OBEY = False
    ITEM_PIPELINES = {
       'meizitu.pipelines.MeizituPipeline': 300,
    }
    DOWNLOAD_DELAY =1
    

    5、main.py

    #coding:utf-8
    from scrapy import cmdline
    cmdline.execute("scrapy crawl meizituespider".split())
    

    执行main.py即可输出数据存入数据库

    三、对比下单独py脚本实现的逻辑 下篇文章做下对比

    忙的很框架和单独py脚本下载图片保存本地的部分,单独py脚本也没写存库

    #coding:utf-8
    import requests
    from lxml import etree
    headers = {
        'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36',
        'Connection': 'keep-alive'
    }
    def parse(url):
        html=requests.get(url,headers=headers).content
        selector=etree.HTML(html)
        infos=selector.xpath('//li[@class="wp-item"]//h3[@class="tit"]')
        print len(infos)
        for info in infos:
            fengmian_url=info.xpath('a/@href')[0]
            fengmian_names = info.xpath('a/text()')
            if len(fengmian_names )>0:
                fengmian_name=fengmian_names[0]
            else:
                fengmian_name=info.xpath('a/b/text()')[0]
            print fengmian_url,fengmian_name
            #解析出fengmian_url后调用parse_page函数
            #类似 框架中yield Request(url=item['fengmian_url'],meta={'item_1': item}, callback=self.parse_page)
            parse_page(fengmian_url)
        urls = ['http://www.meizitu.com/a/list_1_{}.html'.format(i) for i in range(2, 93)]
        for url in urls:
            # 类似 框架中 yield Request(url=req_url,callback=self.parse)
            parse(url)
    def parse_page(url):
        html=requests.get(url).content
        selector=etree.HTML(html)
        infos=selector.xpath('//div[@class="postContent"]//p//img')
        print len(infos)
        for info in infos:
            image_url=info.xpath('@src')[0]
            image_name=info.xpath('@alt')[0]
            print image_name,image_url
    
    if __name__ == '__main__':
        #下面注释的部分和parse函数里的一样 放在parse函数里是为了和scrapy保持一致 对比 理解
    #     urls=['http://www.meizitu.com/a/list_1_{}.html'.format(i) for i in range(1,91)]
    #     for url in urls:
    #         parse(url)
        #类似start_urls=['http://www.meizitu.com/a/list_1_1.html']
        url='http://www.meizitu.com/a/list_1_1.html'
        parse(url)
    

    四、总结

    1、细心。

    ①items.py Field字段在爬虫主程序里item的key值一定完全一致,昨晚调试一个字段多了个空格导致失败,并且不容易发现
    ②name为爬虫名和项目名称不能重复,start_urls,allowed_domains这两个变量命名不能变,都是列表类型, allowed_domains包含了spider允许爬取的域名(domain)列表(list),这样写allowed_domains = ["meizitu.com"] ,不要写一个具体的url页面如allowed_domains = ['http://www.meizitu.com/a/list_1_1.html']
    ③字典类型 取值应该是 字典[key] 中括号

    2、对网页源码进行解析。

    找你最拿手的,xpath,bs4,正则等可以灵活运用,xpath语法在单独脚本和scrapy中不同,框架需要加extract()才可以取出数据,很多人会犯这个错,如果以后用框架推荐xpath

    3、学习方法

    初学爬虫不建议上来用框架,先写单独的py脚本,封装函数,函数调用,字典
    for循环、操作数据库操作比用框架要实用的多,对想要的数据结构或者结果有良好的思维是第一步,不然一切都是徒劳,切忌求快

    相关文章

      网友评论

      • 谁占了我的一年的称号:这个main函数是什么意思?为什么我在pycharm里运行别的爬虫还是会报错?
        chengcxy:@谁占了我的一年的称号 和cmd命令启动爬虫一样 引入了cmd方法呢不是 你报什么错

      本文标题:Scrapy抓取多层网页结构详解(三)

      本文链接:https://www.haomeiwen.com/subject/oimkattx.html