欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

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

程序员文章站 2024-03-22 16:40:10
...

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

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

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

首先我们打开fiddler(抓包工具),打开淘宝搜索“空军一号”,通过进入首页我们在fiddler中截获到了几条请求,如图:
抓取淘宝信息(scrapy框架,ajax请求的分析)
从图中可以看到那几条请求中字节数较多的只有两条,很有可能我们的首页数据就藏在这个里面。
实际上也确实是,首页中的48个商品信息分为两个请求,在两个响应中。第一个是一个标准的html文件,第二个的格式是一个json型的数据,第二个里面放着后面12个商品的信息,那么前36个的信息就在第一个请求中。我们来着重看一下第一个请求。
可以在响应的主体中看到也藏有json型的数据,如图中框起来的!
抓取淘宝信息(scrapy框架,ajax请求的分析)
那么,那么我么将图中的json数据用正则匹配出来,然后如同json数据一样进行信息提取不就行了!
我们首先分析一下这个json数据,看看格式是怎么样的!
直接复制这一段类似于json型的数据,由于有点乱,所以我们可以用一个格式化的方法,我这里用的是notep++的JSON Viewer插件,把数据复制进去,然后就可以通过运行插件将json的数据已格式化的方式呈现出来。
如图:
抓取淘宝信息(scrapy框架,ajax请求的分析)
如图中框出来的就是插件位置,然后数据我们已经完全格式化成为我们所需要的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页的请求结果)

我们点击下一页,看抓包工具又多了哪些请求如图:
抓取淘宝信息(scrapy框架,ajax请求的分析)

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

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

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

继续下行,我们要找到第三页,第四页的变化参数在那呀!
对于第三页的参数,我们当然是从它的跳转页面的响应中找变化的参数了,
第三页的链接如下:
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数据,我们只好一个标签一个标签的找:
最终在这里
抓取淘宝信息(scrapy框架,ajax请求的分析)
如图中,我们最终在第二页的响应体的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()

好了,基本上就是这样,我们已经爬去了关于所有‘空军一号’信息了,当然改良一下就可以爬取其他商品了。
成果贴一下:
抓取淘宝信息(scrapy框架,ajax请求的分析)
可以看到我们已经爬取了5000条的信息了,算算,每页44个,一共也差不多!爬完了。

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