聚美优品APP8.725接口分析获得商家列表数据


0x01、 目标需求:

  .a) a) 分析聚美优品APP的接口参数以及相关的签名获取的方式

  .b) b) 需要获取全品类分类接口,以及分类下商品列表接口

0x02、分析背景:

  .a) 聚美优品APP版本V8.725(目前最新版)

  .b) 软件无壳

  .c) 软件通讯过程中无数据加密,反爬策略仅针对IP访问频率

0x03、分析流程:

  .a) 通过数据包抓取或敏感函数hook方式获得接口功能

  .b) 通过函数内部参数的组装继续分析参数的来源以及加密的流程

0x04、接口分析:

  .a) 分类接口抓包分析:

    0x001.分类api接口

分类接口抓包



    0x002. 返回数据解析(以下链接抓到的获取分类数据的接口信息.包含所有分类数据,图片,分类ID,一级、二级、三级、分类)

返回JSON数据解析

ab参数是死值,服务器返回来的多个APP版本中未改变。可以写死

json数据



    0x003. JSON数据解析Python打印后,如下图:

python处理分类



    0x004. 分类的商品列表接口数据抓取,如下图:

列表数据

商品列表数据



Python代码如下,直接可以获取数据

import requests,json,re,time,urllib.parse
requests.packages.urllib3.disable_warnings()

headers = {
        'User-Agent': 'JuMei/8.725 (M2003J15SC; Android; Android OS ; 10; zh) ApacheHttpClient/4.0',
        'X-Jumei-Authorization': '',
        'Host': 'api.jumei.com',
        'Accept-Encoding': 'GZIP',
        'X-Online-Host': 'api.jumei.com',
        'Sm-Device-Id': '20200716113240e5fcda73e076fbc4d42013e9105cdfee00fc944ffba6f31b',
        'X-Tingyun-Id': 'Dh-NvlyTByI;c=2;r=1500178814;',
        'x_jm_owl_req_id': 'originalRequest.id',
        'Connection': 'Keep-Alive',
        'Cookie': 'default_site_25=bj; JSESSIONID=BDAA47A22F344C9DD9639BD7CFB27421; language=zh; source=qq-appstore; resolution=1080*2110; platform=android; mac=6ae449329df4c1ca; network=wifi; httpDnsVersion=26; httpDnsCase=1; model=M2003J15SC; unique_device_id=Xw/KTwrssXEDAAS3xFry5L5Z; PHPSESSID=01c2027bd6274afc788b2f78fd8f1d94; install_source=qq-appstore; brand=Redmi; user_ip=192.168.1.3; ab=3:d|7:e|123:b|131:d|150:a|160:c|164:v8|166:b|177:video2t10|179:video3t2|186:video4t19|191:c|281:v8|666:b|703:wvgre_a|909:a|1001:b|1200:a10|1300:normal|1302:a10|1655:v8|1806:normal|1807:j|1808:a19|1809:a19|1810:normal|6680:a|8998:b|9081:c|9108:videofeedb|9902:f|73163:d|73164:f; product=jumei; device_id=6ae449329df4c1ca; uc_api_session_id=5f0fca58466394937; user_tag_id=131; client_v=8.725; postcode=110000; platform_v=10; appfirstinstall=1; site=bj; device_uid=59df0cad-856c-4e49-a3dd-39f1888cb3c1; referer_site=app_android_com.jm.android.jumei_qq-appstore_v8.725_site; appid=com.jm.android.jumei; appsecret=114ab1fa; imei=6ae449329df4c1ca; android_id=6ae449329df4c1ca; is_first_open=1'
}

# 获取分类信息api
categoryUrl = "https://api.jumei.com/Search/Category?ab=3%3Ad%7C7%3Ae%7C123%3Ab%7C131%3Ad%7C150%3Aa%7C160%3Ac%7C164%3Av8%7C166%3Ab%7C177%3Avideo2t10%7C179%3Avideo3t2%7C186%3Avideo4t19%7C191%3Ac%7C281%3Av8%7C666%3Ab%7C703%3Awvgre_a%7C909%3Aa%7C1001%3Ab%7C1200%3Aa10%7C1300%3Anormal%7C1302%3Aa10%7C1655%3Av8%7C1806%3Anormal%7C1807%3Aj%7C1808%3Aa19%7C1809%3Aa19%7C1810%3Anormal%7C6680%3Aa%7C8998%3Ab%7C9081%3Ac%7C9108%3Avideofeedb%7C9902%3Af%7C73163%3Ad%7C73164%3Af&android_id=6ae449329df4c1ca&antifraud_sign=d06f112b5c34a4ce44d31ea3d9d2f1b3&antifraud_tid=3615756017&antifraud_ts="+str(int(time.time()))+"&appfirstinstall=1&client_v=8.725&global_init_log_device_model=m2003j15sc&global_init_log_device_vendor=xiaomi&global_init_log_os_version=10&global_init_log_push_enable=on&global_init_log_push_id=bd02330118a9043c61cc88a287e630c9&is_first_launch=0&is_first_open=1&platform=android&site=bj&source=qq-appstore&unique_device_id=Xw%2FKTwrssXEDAAS3xFry5L5Z&user_tag_id=131"
# 获取商品列表api
shopListUrl = "https://api.jumei.com/Search/Data?ab=3%3Ad%7C7%3Ae%7C123%3Ab%7C131%3Ad%7C150%3Aa%7C160%3Ac%7C164%3Av8%7C166%3Ab%7C177%3Avideo2t10%7C179%3Avideo3t2%7C186%3Avideo4t19%7C191%3Ac%7C281%3Av8%7C666%3Ab%7C703%3Awvgre_a%7C909%3Aa%7C1001%3Ab%7C1200%3Aa10%7C1300%3Anormal%7C1302%3Aa10%7C1655%3Av8%7C1806%3Anormal%7C1807%3Aj%7C1808%3Aa19%7C1809%3Aa19%7C1810%3Anormal%7C6680%3Aa%7C8998%3Ab%7C9081%3Ac%7C9108%3Avideofeedb%7C9902%3Af%7C73163%3Ad%7C73164%3Af&android_id=6ae449329df4c1ca&antifraud_sign=d22dfff9041604f361eb9d7db117f7a7&antifraud_tid=3615756017&antifraud_ts="+str(int(time.time()))+"&appfirstinstall=1&auto_correct=1&category_id={}&client_v=8.725&global_init_log_device_model=m2003j15sc&global_init_log_device_vendor=xiaomi&global_init_log_os_version=10&global_init_log_push_enable=on&global_init_log_push_id=bd02330118a9043c61cc88a287e630c9&is_first_launch=0&is_first_open=1&is_first_search=1&is_together_filter_brand=true&item_per_page=20&page={}&platform=android&search={}&search_source=category&search_source_ex=main_search&search_type=mSearch&site=bj&source=qq-appstore&unique_device_id=Xw%2FKTwrssXEDAAS3xFry5L5Z&user_tag_id=131"
def get_category():
    res = requests.get(url=categoryUrl,headers=headers,verify=False)
    return res


def get_item_list(categoryId,page,keyWord):
    url = shopListUrl.format(categoryId,page,keyWord)
    res = requests.get(url=url,headers=headers,verify=False)
    j = json.loads(res.text)['data']
    print("\t|\t\t\t|\t\t\t|")
    try:
        print("\t|\t\t\t|\t\t\t|=====================>当前:页码[%s],总页数[%s],商品数量[%s],每页条数[%s]" % (j['page'],j['page_count'],j['item_count'],j['item_per_page']))
    except:
        print(res.text)
        print(url)
    for i in j['item_list_component']:
        for t in i['title']:
            if t['type'] == 'main':
                title = t['desc']
        print("\t|\t\t\t|\t\t\t|============================>当前:商品ID[%s],商品标题[%s],商品类型[%s]" % (i['info']['item_id'],title,i['info']['type']))
    page = int(page)
    if page < int(j['page_count']):
        page += 1
        get_item_list(categoryId,page,keyWord)

if __name__ == '__main__':
    res = get_category()
    data = json.loads(res.text)
    category = data['data']['category']
    for i in category:
        if ('推荐' in i['name']) or ('热门' in i['name']) or ('热门' in i['name']):
            continue
        print("\t%s" % i['name'])
        for j in i['second_level']:
            if ('推荐' in j['name']) or ('热门' in j['name']) or ('大牌' in j['name']) or ('品牌' in j['name']):
                continue
            print("\t|")
            print("\t|-------->%s" % j['name'])
            for k in j['three_level']:
                print("\t|\t\t\t|")
                print("\t|\t\t\t|-------->%s[分类ID:%s]" % (k['name'],re.findall("\d+",k['link'])[0]))
                get_item_list(re.findall("\d+",k['link'])[0],"1",urllib.parse.quote(k['name']))


在获取商品列表页时有三个参数其中有一个是签名,但是服务器并没有做校验.签名生成代码如下Java版,主要是对uri进行加密后再吧参数追加进去,暂时不需要.
import org.apache.commons.lang3.StringUtils;

import java.net.URLDecoder;
import java.security.MessageDigest;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;

public class JumeiParamTest {

    public static void main(String[] args) {
        long currentUnixTime = System.currentTimeMillis()/1000;
        String url = "https://api.jumei.com/Search/Data?";
        String uri = "source=qq-appstore&global_init_log_device_model=m2003j15sc&platform=android&is_first_launch=0&search=女香&category_id=35&global_init_log_os_version=10&search_source_ex=main_search&unique_device_id=Xw/KTwrssXEDAAS3xFry5L5Z&auto_correct=1&ab=3:d|7:e|123:b|131:d|150:a|160:c|164:v8|166:b|177:video2t10|179:video3t2|186:video4t19|191:c|281:v8|666:b|703:wvgre_a|909:a|1001:b|1200:a10|1300:normal|1302:a10|1655:v8|1806:normal|1807:j|1808:a19|1809:a19|1810:normal|6680:a|8998:b|9081:c|9108:videofeedb|9902:f|73163:d|73164:f&user_tag_id=131&client_v=8.725&is_together_filter_brand=true&global_init_log_push_id=bd02330118a9043c61cc88a287e630c9&global_init_log_push_enable=on&search_source=category&search_type=mSearch&item_per_page=20&appfirstinstall=1&site=bj&is_first_search=1&page=1&android_id=6ae449329df4c1ca&global_init_log_device_vendor=xiaomi&is_first_open=1";
        Map<String,String> map = strToMap(uri);
        map.remove("uid");
        map.remove("antifraud_sign");
        map.remove("antifraud_ts");
        map.remove("antifraud_tid");
        Object[] keys = map.keySet().toArray();
        Arrays.sort(keys);
        StringBuilder sb = new StringBuilder();
        for (Object key : keys) {
            if (!StringUtils.isEmpty(map.get(key))) {
                sb.append(map.get(key));
            }
        }
        sb.append(currentUnixTime);
        sb.append("6b271087cea77e53b635fd24cb99525bv:v3");
        String s = sb.toString();
        System.out.println(s);
        try {
            byte[] btInput = s.getBytes("UTF-8");
            MessageDigest md5 = MessageDigest.getInstance("MD5");
            md5.update(btInput);
            byte[] bs = md5.digest();
            sb = new StringBuilder();
            for (byte b2 : bs) {
                sb.append(Integer.toHexString((b2 & -1) | -256).substring(6));
            }
            System.out.println(sb.toString());
        } catch (Exception e2) {
            e2.printStackTrace();
        }

    }


    public static Map<String,String> strToMap(String str){
           String[] strs = str.split("&");
           Map<String,String> m = new HashMap<>();
           StringBuffer sb = new StringBuffer();
           for (String s :strs){
               m.put(s.split("=")[0],s.split("=")[1]);

           }
           return m;
    }
}


待解决问题:爬取商品列表页不能超过100页,超过后不返回数据

友情提示:本文只为技术分享交流,请勿非法用途.产生一切法律问题与本人无关



在浏览的同时希望给予作者打赏,来支持作者的服务器维护费用.一分也是爱~