抓取淘宝信息(scrapy框架,ajax请求的分析)

今天用scrapy框架抓取淘宝信息,因为淘宝的页面都是一个网关程序加载实现的,所以可以说每个页面的信息会随着每一次的刷新有所不同。当然这个我只是普及一下,跟我们今天的抓取关系不大

首先今天的抓取主要的内容是分析ajax请求,然后构造请求。实现的话我用的是scrapy框架,但是用requests也是可以的,只不过抓取的会慢一点而已!所以着重讲的是分析ajax请求,OK?

好了,废话有点多!
今天抓取的是商品“空军一号”的所有搜索结果

首先我们打开fiddler(抓包工具),打开淘宝搜索“空军一号”,通过进入首页我们在fiddler中截获到了几条请求,如图:
这里写图片描述
从图中可以看到那几条请求中字节数较多的只有两条,很有可能我们的首页数据就藏在这个里面。
实际上也确实是,首页中的48个商品信息分为两个请求,在两个响应中。第一个是一个标准的html文件,第二个的格式是一个json型的数据,第二个里面放着后面12个商品的信息,那么前36个的信息就在第一个请求中。我们来着重看一下第一个请求。
可以在响应的主体中看到也藏有json型的数据,如图中框起来的!
这里写图片描述
那么,那么我么将图中的json数据用正则匹配出来,然后如同json数据一样进行信息提取不就行了!
我们首先分析一下这个json数据,看看格式是怎么样的!
直接复制这一段类似于json型的数据,由于有点乱,所以我们可以用一个格式化的方法,我这里用的是notep++的JSON Viewer插件,把数据复制进去,然后就可以通过运行插件将json的数据已格式化的方式呈现出来。
如图:
这里写图片描述
如图中框出来的就是插件位置,然后数据我们已经完全格式化成为我们所需要的json型的数据,接下来只是对首页的数据进行提取就行了。
这个首页的数据并没有动态改变的请求体(当然除了时间戳),所以可以直接请求即可,别忘了加上几个请求头,防止被识别,大致代码如下

pattern = re.compile('g_page_config = ({.*?});', re.S)
# 用正则匹配出json数据
json_data = re.search(pattern, response.text).group(1)
json_l = json.loads(json_data)
# 这里我们就得到了那个json型的数据

具体的提取过程我就不详细说了,就是json数据的提取,别忘了加几个请求头

headers = {
    'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.162 Safari/537.36',
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8',
'Accept-Encodin': 'gzip, deflate, br',
    'Accept-Language': 'zh-CN,zh;q=0.9',
    'Referer': 'https://www.taobao.com/',
'Host': 's.taobao.com'
}

这是第一个36个商品的请求,和对数据的提取,接下来就是那个12个商品的请求,这个请求加了一个时间戳请求体
请求如下:
https://s.taobao.com/api?_ksTS=1527320555634_238&callback=jsonp239&ajax=true&m=customized&sourceId=tb.index&q=%E7%A9%BA%E5%86%9B%E4%B8%80%E5%8F%B7&spm=a21bo.2017.201856-taobao-item.1&s=36&imgfile=&initiative_id=tbindexz_20170306&bcoffset=-1&commend=all&ie=utf8&rn=07ca89f6b994ae6b55d0c874c3dda726&ssid=s5-e&search_type=item
上面请求中的斜体部分就是加入的时间戳,是一个毫秒级的时间戳,下划线后面的部分我们可以随机生成一个三位或四位的数字,callback加一就可以了,具体构造这两个参数,函数如下:

import time,random
time_stamp = int(round(time.time()*1000))
# 生成毫秒级的时间戳
ran_num = random.randint(100,3000)
# 在100到3000之间随机生成一个数
ksts_str = str(time_stamp) + '_' +str(ran_num)
# 这个就是ksts后面的值
callback_str = 'jsonp' + str(ran_num+1)
# 这个是callback后面的值
# 具体上面代码你有看不懂的可以去百度一下,这里主要是构造的思路 

这样我们就可以构造那十二个商品的请求了,这个请求只有时间戳会改变,其他的也是固定不变的。
这里说明一下,由于这两个请求都是首页的请求,所以参数并不会有很大的变化,只有更深层次的请求会从从前面的响应中提取一些数据作为参数,在这里理解请求头中的referer请求头很重要

referer请求头,指明该请求是从哪个请求跳转过来的。,一般我们找到这个跳转的请求的响应体,可以从响应体重找到下一个请求的参数(有点绕口,望仔细品读)

好了,从上面的两步,我们已经提取了首页的全部48个商品的信息了,接下来还有99个页面等着我们去提取(一共有100页的请求结果)

我们点击下一页,看抓包工具又多了哪些请求如图:
这里写图片描述

如图我们翻到第二页,看到返回的是一个全是json型的数据,那对数据的提取并不难,但是有一个难题的这个请求的请求体很多,经过测试,其中除了时间戳外还有好几项都是改变的。
所以接下来我们重点分析的是请求体,在哪里可以找到请求体,只有找到了哪些变化的请求体我们才能构造出接下来的请求,实现自动化的抓取(对json型数据的提取我就不说了,很简单的。)

扫描二维码关注公众号,回复: 1136206 查看本文章

我们要用到fiddler的关键字搜索功能,既然是第二页的请求, 而这个请求又是 从图中的请求跳转过来的,那么我们就一定可以从跳转请求的响应体中找到这些请求体
这里写图片描述
图中框出来的请求就是第一个36个商品的请求,那么我们在它的响应体重搜索
如图:
这里写图片描述
至于为什么按那个关键字搜索请求体,且听下面更多的分解。
到这个我们已经找到了这个可变的参数在哪里(这里你不明白就往下看,下面有更详细的原因解释!!!!)

好了,经过上面的解析,我们已经知道了该去哪找变化的参数(仅限于第二页的变化参数)

继续下行,我们要找到第三页,第四页的变化参数在那呀!
对于第三页的参数,我们当然是从它的跳转页面的响应中找变化的参数了,
第三页的链接如下:
https://s.taobao.com/search?data-key=s&data-value=88&ajax=true&_ksTS=1527324090969_1099&callback=jsonp1100&q=%E7%A9%BA%E5%86%9B%E4%B8%80%E5%8F%B7&imgfile=&commend=all&ssid=s5-e&search_type=item&sourceId=tb.index&spm=a21bo.2017.201856-taobao-item.1&ie=utf8&initiative_id=tbindexz_20170306&bcoffset=0&ntoffset=6&p4ppushleft=1%2C48&s=44**其中加粗的值会改变的参数**
由于第二页的响应是一个比较纯的json数据,我们只好一个标签一个标签的找:
最终在这里
这里写图片描述
如图中,我们最终在第二页的响应体的pager这一个链接地址找到了第三页的链接参数,复制过来如下:
pager=//s.taobao.com/search?q=%E7%A9%BA%E5%86%9B%E4%B8%80%E5%8F%B7&imgfile=&commend=all&ssid=s5-e&search_type=item&sourceId=tb.index&spm=a21bo.2017.201856-taobao-item.1&ie=utf8&initiative_id=tbindexz_20170306&bcoffset=0&ntoffset=6&p4ppushleft=1%2C48&s=44
注意看这个加粗的是不是和第三页请求中变化的参数是一样的。
接下来我们只要加工一下这个请求就可以构造出第三页的链接了。
查看pager所表示的链接中少了那几个参数,加进去就可以构造出第三页的链接了。

        next_partial_url = json_l.get('mainInfo').get('modLinks').get('pager')
        time = self.get_time_stamp()
        callback = 'jsonp' + str(int(time[1]) + 1)
        data_value = 44
        # 每一页的data-value要加上44,这是一个有规律的变化参数
        other_data = {
        'data-key' : 's',
        'data-value' : str(data_value),
        'ajax' : 'true',
        '_ksTS': time[0],
        'callback' : callback
        }
        # 这是需要补齐的参数
        next_url = 'https:' + next_partial_url + '&' + parse.urlencode(other_data)
        # 这里我们构造出了下一页的链接

现在知道我们前面的搜索的关键字为什么是modlinks了吗?因为从后面看,pager这个就是包含在modlinks里面的
**好了,这样我们就能通过这种方式不断地构造出后面的链接了,一直到100页。
详细代码我贴出来,使用scrapy实现的,可以认真的看一下,对于新手来说,有一些必须掌握的方法在里面,对了,看了这个我相信你对scrapy和python的类会有一个更深的了解**

spider文件:taobaospider.py

# -*- coding: utf-8 -*-
import scrapy
import time,re,json,random
from time import sleep
from urllib import parse
from scrapy.http import Request
from taobao.items import TaobaoItem



class TaobaospiderSpider(scrapy.Spider):
    name = 'taobaospider'
    allowed_domains = ['taobao.com']

    def get_time_stamp(self):
        time_stamp = int(round(time.time() * 1000))
        ran_num = random.randint(100, 3000)
        ksts = str(time_stamp) + '_' + str(ran_num)
        return ksts,str(ran_num)
     # 这里我直接声明了一个方法,用于获取时间戳和callback,返回两者的元组。

    def start_requests(self):
    # 这里我们去掉了start_urls,直接用这个方法来构造初始请求
        data_fir = {
            'q' : '空军一号',
            'imgfile' : '',
            'commend' : 'all',
            'ssid' : 's5-e',
            'search_type' : 'item',
            'sourceId' : 'tb.index',
            'spm' : 'a21bo.2017.201856-taobao-item.1',
            'ie' : 'utf8',
            'initiative_id' : 'tbindexz_20170306'

        }
        url = 'https://s.taobao.com/search?' + parse.urlencode(data_fir)
        # 构造参数的链接的方式
        yield Request(url=url,callback=self.parse)

        time = self.get_time_stamp()
        callback = 'jsonp' + str(int(time[1])+1)
        # 时间戳和callback参数的构造
        data_sec = {
            '_ksTS' : time[0],
            'callback' : callback,
            'ajax' : 'true',
            'm' : 'customized',
            'sourceId' : 'tb.index',
            'q' : '空军一号',
            'spm' : 'a21bo.2017.201856-taobao-item.1',
            's' : '36',
            'ie': 'utf8',
            'initiative_id': 'tbindexz_20170306',
            'bcoffset' : '-1',
            'imgfile' : '',
            'rn'  : '15339ec798bab63adebc3d45909aaaef',
            'ssid' : 's5 - e',
            'search_type' :   'item'
        }
        urls = 'https://s.taobao.com/api?' + parse.urlencode(data_sec)
        yield Request(url=urls,callback=self.parse_sec)

    def parse_sec(self,response):
        json_html = re.search('jsonp\d+?\(({.*?})\)', response.text, re.S).group(1)
        json_htmll = json.loads(json_html)
        item = TaobaoItem()
        for datas in json_htmll.get('API.CustomizedApi').get('itemlist').get('auctions'):
            item['item_loc'] = datas.get('item_loc')
            item['pic_url'] = 'https:' + datas.get('pic_url')
            item['raw_title'] = datas.get('raw_title')
            item['shop_link'] = datas.get('shop_link')
            item['view_price'] = datas.get('view_price')
            item['view_sales'] = datas.get('view_sales')
            file_names = ['item_loc', 'pic_url', 'raw_title', 'view_price', 'view_sales', 'shop_link']
            for name in file_names:
                if item[name] == None:
                    item[name] = 'there is no item data'
            yield item

    def parse(self, response):
        pattern = re.compile('g_page_config = ({.*?});', re.S)
        # 匹配出json型的数据,用正则。
        json_data = re.search(pattern, response.text).group(1)
        json_l = json.loads(json_data)
        item = TaobaoItem()
        for datas in json_l.get('mods').get('itemlist').get('data').get('auctions'):
            item['item_loc'] = datas.get('item_loc')
            item['pic_url'] = 'https:' + datas.get('pic_url')
            item['raw_title'] = datas.get('raw_title')
            item['shop_link'] = datas.get('shop_link')
            item['view_price'] = datas.get('view_price')
            item['view_sales']= datas.get('view_sales')
            file_names = ['item_loc', 'pic_url', 'raw_title', 'view_price', 'view_sales', 'shop_link']
            for name in file_names:
                if item[name] == None:
                    item[name] = 'there is no item data'
            yield item
        next_partial_url = json_l.get('mainInfo').get('modLinks').get('pager')
        time = self.get_time_stamp()
        callback = 'jsonp' + str(int(time[1]) + 1)
        data_value = 44
        other_data = {
        'data-key' : 's',
        'data-value' : str(data_value),
        'ajax' : 'true',
        '_ksTS': time[0],
        'callback' : callback
        }
        next_url = 'https:' + next_partial_url + '&' + parse.urlencode(other_data)
        # 这里获得第二页的基本链接,要加上时间戳
        data_values = data_value + 44
        yield Request(url=next_url,meta={'data_value' : data_values},callback=self.parse_item)
        # meta参数用于传递页数




    def parse_item(self,response):
        str_data = re.search('jsonp\d+?\(({.*?})\)',response.text,re.S).group(1)
        with open('D:/taobao/x.txt','w') as f:
            f.write(str_data)
        json_data = json.loads(str_data)
        itemlist = json_data.get('mods').get('itemlist').get('data').get('auctions')

        for datas in itemlist:
            item = TaobaoItem()
            item['item_loc'] = datas.get('item_loc')
            item['pic_url'] = 'https:' + datas.get('pic_url')
            item['raw_title'] = datas.get('raw_title')
            item['shop_link'] = datas.get('shop_Link')
            item['view_price'] = datas.get('view_price')
            item['view_sales'] = datas.get('view_sales')
            file_names = ['item_loc', 'pic_url', 'raw_title', 'view_price', 'view_sales', 'shop_link']
            for name in file_names:
                if item[name] == None:
                    item[name] = 'there is no item data'
            yield item
        pager = json_data.get('mainInfo').get('modLinks').get('pager')
        print('爬取一页结束')
        # 调试信息
        # 这里获得了下一页的基本链接,还有加上时间戳
        time = self.get_time_stamp()
        callback = 'jsonp' + str(int(time[1]) + 1)
        other_data = {
            'data-key': 's',
            'data-value': str(response.meta['data_value']),
            'ajax': 'true',
            '_ksTS': time[0],
            'callback': callback
        }
        next_url = 'https:' + pager + '&' + parse.urlencode(other_data)
        # 这里获得第二页的基本链接,要加上时间戳
        data_values = response.meta['data_value'] + 44
        yield Request(url=next_url,meta={'data_value':data_values}, callback=self.parse_item)



基本上就是这样,这里有什么不懂得可以私信我!
管道文件:pipelines.py

import csv
class TaobaoPipeline(object):
    def open_spider(self,spider):
        self.file = open('D://taobao/淘宝信息.csv','w')

    def process_item(self, item, spider):
    # 将信息写进csv文件中
        file_names = ['item_loc','pic_url','raw_title','view_price','view_sales','shop_link']
        for name in file_names:
            if item[name] == None:
                item[name] = 'there is no item data'
        dict_writer = csv.DictWriter(self.file,fieldnames=file_names)
        dict_writer.writerow(dict(item))
        return item

    def close_item(self,spider):
        self.file.close()

csv内置模块了解一下?
设置文件:settings.py

DEFAULT_REQUEST_HEADERS = {
    'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.162 Safari/537.36',
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8',
'Accept-Encodin': 'gzip, deflate, br',
    'Accept-Language': 'zh-CN,zh;q=0.9',
    'Referer': 'https://www.taobao.com/',
'Host': 's.taobao.com'
}

主要是请求头的设置
tiem文件:items.py

from scrapy import Item,Field


class TaobaoItem(Item):
    item_loc = Field()
    pic_url = Field()
    raw_title = Field()
    view_price = Field()
    view_sales = Field()
    shop_link = Field()

好了,基本上就是这样,我们已经爬去了关于所有‘空军一号’信息了,当然改良一下就可以爬取其他商品了。
成果贴一下:
这里写图片描述
可以看到我们已经爬取了5000条的信息了,算算,每页44个,一共也差不多!爬完了。

有什么问题私信和我交流!

猜你喜欢

转载自blog.csdn.net/killeri/article/details/80463303