Greasy Fork is available in English.

星图达人画像

星图扩展工具

// ==UserScript==
// @name         星图达人画像
// @namespace    http://tampermonkey.net/
// @version      0.5.1
// @description  星图扩展工具
// @author       siji-Xian
// @match        *://www.xingtu.cn/ad/creator/market
// @icon         https://www.google.com/s2/favicons?domain=oceanengine.com
// @grant        none
// @license      MIT
// @require      https://lf26-cdn-tos.bytecdntp.com/cdn/expire-1-M/jquery/3.2.1/jquery.min.js
// @require      https://cdn.bootcss.com/moment.js/2.20.1/moment.min.js
// @require      https://greasyfork.org/scripts/404478-jsonexportexcel-min/code/JsonExportExcelmin.js?version=811266
// @require      https://greasyfork.org/scripts/455576-qmsg/code/Qmsg.js?version=1122361
// ==/UserScript==

(function () {
  "use strict"; 
  var new_element = document.createElement("link");
  new_element.setAttribute("rel", "stylesheet");
  new_element.setAttribute("href", "https://qmsg.refrain.xyz/message.min.css");
  document.body.appendChild(new_element);

  const button = document.createElement("div");
  button.textContent = "导出画像";
  Object.assign(button.style, {
    height: "34px",
    lineHeight: "var(--line-height, 34px)",
    alignItems: "center",
    color: "white",
    background: "linear-gradient(90deg, rgba(0, 239, 253), rgba(64, 166, 254))",
    borderRadius: "34px",
    marginLeft: "10px",
    fontSize: "13px",
    padding: "0 20px",
    cursor: "pointer",
    fontWeight: "500",
  });
  button.addEventListener("click", urlClick);

  function appendDoc() {
    const likeComment = document.querySelector(".operations");
    if (likeComment) {
      likeComment.append(button);
      return;
    }
    setTimeout(appendDoc, 1000);
  }
  appendDoc();

  //message.js
  let loadingMsg = null;

  //目标数据
  let target_data_param = null;

  (function listen() {
    var origin = {
      open: XMLHttpRequest.prototype.open,
      send: XMLHttpRequest.prototype.send,
    };
    XMLHttpRequest.prototype.open = function (a, b) {
      this.addEventListener("load", replaceFn);
      origin.open.apply(this, arguments);
    };
    XMLHttpRequest.prototype.send = function (a, b) {
      origin.send.apply(this, arguments); 
    };
    function replaceFn(obj) {
      if (
        this?._url?.slice(0, 40) == "/gw/api/gsearch/search_for_author_square"
      ) {
        target_data_param = JSON.parse(this._data);
      }
    }
  })();

  function getQueryVariable(variable) {
    var query = window.location.search.substring(1);
    var vars = query.split("&");
    for (var i = 0; i < vars.length; i++) {
      var pair = vars[i].split("=");
      if (pair[0] == variable) {
        return pair[1];
      }
    }
    return false;
  }
   //获取aadvid
   const aadvid = getQueryVariable("aadvid");


  const getRequestOptions = {
    method: "GET",
    redirect: "follow",
  };

  async function fetchFun(url, data, requestOptions = getRequestOptions) {  
    const params = new URLSearchParams(data).toString();  
    try {  
      const response = await fetch(`${url}?${params}`, requestOptions);  
      if (response.ok) {  
        let result;  
        try {  
          result = await response.json();  
        } catch (error) {  
          throw new Error(`响应不能被解析为JSON: ${error.message}`);  
        }  
        return result;  
      } else {  
        throw new Error(`Fetch failed: ${response.status}`);  
      }  
    } catch (error) {  
      loadingMsg.close();  
      Qmsg.error({  
        content: `网络请求错误: ${error.message}`,  
        timeout: 5000  
      });  
      throw error;  
    }  
}

  async function getProportion(num,all) {
    let allNum = all.reduce((a, b) => {
      return +a + +b?.distribution_value;
    }, 0);

    return num/allNum
  }

  //获取达人列表
  async function task_list(page) {
    loadingMsg = Qmsg.loading("正在导出,请勿重复点击!");
    let {startPage, endPage} = page
    let data = [];
    var myHeaders = new Headers();
    myHeaders.append("authority", "www.xingtu.cn");
    myHeaders.append("accept", "application/json, text/plain, */*");
    myHeaders.append("accept-language", "zh-CN,zh;q=0.9");
    myHeaders.append("agw-js-conv", "str");
    myHeaders.append("content-type", "application/json");
    const postRequestOptions = {
      method: "POST",
      body: '',
      headers: myHeaders,
      redirect: "follow",
    };
    for (let i = startPage; i <= endPage; i++) {
      target_data_param.page_param.page = i;
      postRequestOptions.body = JSON.stringify(target_data_param)
      const targetPromise = await fetchFun(
        "https://www.xingtu.cn/gw/api/gsearch/search_for_author_square",
        {},
        postRequestOptions
      );
      data.push(targetPromise.authors)
    }
    let dataList = data.flat()
    let portraitData = [];
    for (let i = 0; i < dataList.length; i++) {
      let data = {
        o_author_id: dataList[i].attribute_datas.id.toString(),
        platform_source:1,
        author_type:1
      }
      let data2 = {
        o_author_id: dataList[i].attribute_datas.id.toString(),
        platform_source:1,
        platform_channel:1,
        industry_id:0
      }
      const targetPromise = await fetchFun(
        "https://www.xingtu.cn/gw/api/data_sp/get_author_fans_distribution",
        data
      );
      const targetPromise2 = await fetchFun(
        "https://www.xingtu.cn/gw/api/data_sp/check_author_display",
        data2
      )
      const targetPromise3 = await fetchFun(
        "https://www.xingtu.cn/gw/api/data_sp/author_link_struct",
        data2
      )

      portraitData.push({key:dataList[i].attribute_datas.nick_name,value:new Map(targetPromise.distributions.map(v =>[v.type_display,v])),value2:targetPromise2,value3:targetPromise3.link_struct})
    }

    let expData = await Promise.all(
      portraitData.map(async v=>{
        let city_level_data = v.value.get("城市等级分布")
        let age = v.value.get("年龄分布")
        let phone = v.value.get("设备品牌分布")
        let ecom_big8_audience = v.value.get("八大人群分布")
        let sex = v.value.get("性别分布")

        let city_level_data_list = new Map(city_level_data.distribution_list.map(v =>[v.distribution_key,v.distribution_value])) 
        let age_list = new Map(age.distribution_list.map(v =>[v.distribution_key,v.distribution_value])) 
        let phone_list = new Map(phone.distribution_list.map(v =>[v.distribution_key,v.distribution_value])) 
        let ecom_big8_audience_list = new Map(ecom_big8_audience.distribution_list.map(v =>[v.distribution_key,v.distribution_value])) 
        let sex_list = new Map(sex.distribution_list.map(v =>[v.distribution_key,v.distribution_value])) 

        let yuntu_city_level1_proportion = await getProportion(city_level_data_list.get('一线'),city_level_data.distribution_list)
        let yuntu_city_level2_proportion = await getProportion(city_level_data_list.get('二线'),city_level_data.distribution_list)
        let age24_30_proportion = await getProportion(age_list.get('24-30'),age.distribution_list)
        let age31_40_proportion = await getProportion(age_list.get('31-40'),age.distribution_list)
        let iphone = await getProportion(phone_list.get("iPhone"),phone.distribution_list)
        let ecom_big8_audience_1_proportion = await getProportion(ecom_big8_audience_list.get('新锐白领'),ecom_big8_audience.distribution_list)
        let ecom_big8_audience_2_proportion = await getProportion(ecom_big8_audience_list.get('精致妈妈'),ecom_big8_audience.distribution_list)
        let ecom_big8_audience_3_proportion = await getProportion(ecom_big8_audience_list.get('资深中产'),ecom_big8_audience.distribution_list)
        let ecom_big8_audience_4_proportion = await getProportion(ecom_big8_audience_list.get('小镇青年'),ecom_big8_audience.distribution_list)
        let ecom_big8_audience_5_proportion = await getProportion(ecom_big8_audience_list.get('Z世代'),ecom_big8_audience.distribution_list)
        let female = await getProportion(sex_list.get('female'),sex.distribution_list)
        let link_cnt = v.value2.link_cnt
        let follower = v.value2.follower
        let profundityUser = v.value2.link_cnt? v?.value3[5]?.value - v?.value3[1]?.value : 0

        return {
          name: v.key,
          yuntu_city_level1_proportion,
          yuntu_city_level2_proportion,
          age24_30_proportion,
          age31_40_proportion,
          iphone,
          ecom_big8_audience_1_proportion,
          ecom_big8_audience_2_proportion,
          ecom_big8_audience_3_proportion,
          ecom_big8_audience_4_proportion,
          ecom_big8_audience_5_proportion,
          female,
          link_cnt,
          follower,
          profundityUser
        }
      })
    )

    expExcel(expData)
  }

  function expExcel(e) {

    let contrast = {
      name: "name",
      一线城市占比: "yuntu_city_level1_proportion",
      二线城市占比: "yuntu_city_level2_proportion",
      '24-30': "age24_30_proportion",
      '31-40': "age31_40_proportion",
      iphone: "iphone",
      新锐白领: "ecom_big8_audience_1_proportion",
      精致妈妈: "ecom_big8_audience_2_proportion",
      资深中产: "ecom_big8_audience_3_proportion",
      小镇青年: "ecom_big8_audience_4_proportion",
      Z世代: "ecom_big8_audience_5_proportion",
      女性占比: "female",
      月连接用户数: "link_cnt",
      粉丝数:"follower",
      月深度用户数:"profundityUser"
    };
    

    let option = {};
    option.fileName = `达人画像`; //文件名
    option.datas = [
      {
        sheetName: "",
        sheetData: e,
        sheetHeader: Object.keys(contrast),
        sheetFilter: Object.values(contrast),
        columnWidths: [], // 列宽
      },
    ];
    var toExcel = new ExportJsonExcel(option);
    toExcel.saveExcel();
    loadingMsg.close();
  }

  function urlClick() {
    try {
        let res = prompt("页码,例: 1,2 (起始页和结束页中间用英文逗号分隔)");
        if (res) {
          let [startPage, endPage] = res.split(",");
          startPage = parseInt(startPage);
          endPage = parseInt(endPage);
          if (isNaN(startPage) || isNaN(endPage) || endPage < startPage) {
            throw new Error("页码格式错误!");
          }
          task_list({ startPage, endPage });
        }
      } catch (err) {
        Qmsg.error(err.message);
      }
  }
})();