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