我的网站开发技术经验总结 我的网站开发技术经验总结
首页

fangdown

我的网站开发技术经验总结
首页
  • 认识ESM
  • chrome-talend插件,类似postman
  • sequelize 使用及技巧
  • UML工具Power Designer建表
  • sequelize相关操作文档
  • 解决抖音获取签名及并发的问题
  • 记一次解决抖音分享页混淆字体,字体图标转UID解决方案
  • 获取抖音用户作品列表信息
  • 获取抖音用户作品列表信息-进阶
  • 获取抖音用户作品列表信息-进阶3
  • 如何根据抖音号获取用户信息
  • 获取用户最新视频
    • 需求
    • 分析
    • 坑
    • 相关代码
    • 总结
  • 模块化-import和require的区别
  • eslint规范
  • js容错处理
  • js-数组分组,执行promise
  • reduce使用遇到的问题
  • 正则匹配html的元素内容
  • taro 小程序 弹窗层禁止底部滚动
  • 公众号签名问题
  • CentOS7中MariaDB重置密码
  • nginx多域名配置
  • node访问接口,得到乱码的结果,原因-Accept-Encoding
  • node写文件到json中
  • node抓取html内容
  • Node.js使用ES6语法
  • express 使用cors中间件解决跨域
  • node + express + session + redis 进行持久化缓存
  • node中读取文件夹,获取文件名称
  • pm2常用命令
  • 使用pm2管理后台node服务
  • typescript puppeteer支持window及document属性
  • node读取json文件
  • node中使用redis缓存
  • node + github的webhook完成自动部署
  • vuepress-blog的性能优化-CDN
  • CENTOS7下安装REDIS
  • promise then和catch的学习和使用
  • promise在循环中的串行并行用法
  • puppeteer常用知识
  • centos部署安装puppeteer
  • python的学习和使用
  • Taro+TypeScript - Mobx实践
  • 爬虫系列 --- 反爬机制和破解方法汇总
  • 安全-html转码
  • taro中使用animation动画
  • charles 使用
  • Mac下VSCode设置iTerm2终端样式
  • centos一步步完成站点部署
  • 云闪付做地铁的思路
  • 准备技能
  • 备案pc项目介绍
  • 备案小程序项目介绍
  • 小程序二维码扫码功能
  • 小程序域名组件开发
  • 小程序添加水印
  • 规则引擎优化
  • 记一次hooks代替redux的经历
  • 通过nodejs+koa+stream进行服务端图片代理
  • nodeJs接入log4j日志
  • nodejs+typescript项目中添加全局global属性
  • create-react-app 安装 bizcharts 项目崩溃
  • 使用MutationObserver监控dom的变化
  • 服务器重启后启动相关服务
  • moment国际化的问题
  • 项目经验
fangdown
2019-12-27
目录

获取用户最新视频

# 需求

获取用户最新视频信息(1000位用户)

# 分析

通过用户主页列表可获取相关信息

# 坑

  1. 用户多, 采用串行方式会比较慢, 所以使用并行方式
  2. 在使用puppeteer工具的时候, 因为访问控制的原因,会出现莫名的一些情况,造成没有数据返回, 造成程序暂停或异常,如在获取起签名的时候,没有处罚request事件,没有完成promise回调,增加了倒计时机制去解决问题
  3. 同一个useragent 访问频繁, puppeteer访问页面会空白, 什么都没有, 采用更好useragent的方式去兼容解决
  4. puppeteer内置了很多设备信息,刚好可以拿来用

# 相关代码

发起任务

export const userLatestVideo = async (day: number) => {
  let videoCount = 0;
  const page = 0;
  const pageSize = 10;
  const city = '池州';
  let userCount = 0;
  let deviceIndex = 0; // 设备顺序
  const browserInstance = await getBrowserInstance();
  try {
    const loop = async (
      city: string,
      page: number,
      pageSize: number
    ): Promise<any> => {
      const { rows, count } = await cityUserList(city, Number(page), pageSize);
      // const users =  {
      //   rows: [{
      //     core_user_id: '84492244869'
      //   }],
      //   count: 1
      // };
      // const { rows, count } =users
      userCount = count;
      let chunkArr = [];
      for (let i = 0; i < rows.length; i += 10) {
        chunkArr.push(rows.slice(i, i + 10));
      }
      for (let chunk of chunkArr) {
        const promiseAll: Promise<number>[] = [];
        chunk.forEach((item: Record<string, any>) => {
          deviceIndex = deviceIndex > devices.length - 1 ? 0 : deviceIndex;
          promiseAll.push(
            listByDay(deviceIndex, browserInstance, item.core_user_id, day)
          );
          deviceIndex += 1;
        });
        let result: number[] = [];
        result = await Promise.all(promiseAll);
        console.log('--------------------result', result);
        result.forEach(item => (videoCount += item));
      }
      console.log('---page', page);
      console.log('---pageSize', pageSize);
      console.log('---count', count);
      if (page * pageSize < count) {
        return loop(city, page + 1, pageSize);
      }
      return videoCount;
    };
    await loop(city, page, pageSize);
  } catch (e) {
    log.error('userLatestVideo-error', e);
  }
  // await browserInstance.close();
  return `共${userCount}人, ${videoCount}条记录`;
};
export const listByDay = async (
  deviceIndex: number,
  browserInstance: any,
  uid: string,
  day: number
): Promise<number> => {
  try {
    const result: Array<any> = [];
    const currentiPhone = devices[deviceIndex];
    const { name, userAgent } = currentiPhone;
    const param = (await getRequestParam(browserInstance, uid, name)) as IList;
    console.log('param', !!param);
    if (!param) return Promise.resolve(0);
    param.count = '2';
    param.max_cursor = '0';
    const dayBefor7 = moment().subtract('days', day - 1);
    let timestamp = (param.timestamp = +dayBefor7);
    const res = await loopList(param, result, userAgent);
    console.log('res', res);
    const data =
      res &&
      res
        .filter(
          (item: Record<string, any>) => Number(item.timestamp) > timestamp
        )
        .map((item: Record<string, any>) => {
          return {
            core_user_id: uid,
            aweme_id: item.aweme_id,
            aweme_type: item.aweme_type,
            cover: item.video.cover && item.video.cover.url_list[0],
            desc: item.desc || '标题为空',
            duration: item.video.duration,
            dynamicCover:
              item.video.dynamic_cover && item.video.dynamic_cover.url_list[0],
            height: item.video.height,
            statistics: JSON.stringify(item.statistics),
            create_time: formatDate('yyyy-MM-dd hh:mm:ss', item.timestamp),
            vid: item.video.vid,
            video_src: item.video.play_addr.url_list[0],
            width: item.video.width,
            play_count: item.statistics.play_count,
            share_count: item.statistics.share_count,
            comment_count: item.statistics.comment_count,
            digg_count: item.statistics.digg_count,
            forward_count: item.statistics.forward_count,
          };
        });
    for (const item of data) {
      console.log('item', item);
      await addAuthorVideo(item);
    }
    return new Promise(resolve => {
      resolve(data.length);
    });
  } catch (e) {
    console.log(e);
    return new Promise(resolve => {
      resolve(0);
    });
  }
};
/**
 * 循环视频列表,并汇总到一个数组里
 * @param param 接口参数
 * @returns result 数组集合
 */
const loopList = async (
  param: IList,
  result: Array<any>,
  userAgent: string
): Promise<any> => {
  try {
    // 如果要查询的日期比数据库记录的日期要晚则不继续了,只查询最新记录
    if (
      param.max_cursor !== '0' &&
      param.timestamp &&
      param.timestamp > param.max_cursor
    ) {
      return new Promise(resolve => {
        resolve(result);
        result = [];
      });
    }
    await sleep(200);
    const res = await getList(param, userAgent);
    if (res.aweme_list && res.aweme_list.length) {
      const list = res.aweme_list;
      list[0].timestamp = res.min_cursor;
      if (list[1]) list[1].timestamp = res.max_cursor;
      result.push(...list);
      return loopList(
        { ...param, max_cursor: res.max_cursor },
        result,
        userAgent
      );
    }
    return new Promise(resolve => {
      resolve(result);
      result = [];
    });
  } catch (e) {
    log.error('循环作品列表,发生错误:', e);
    return new Promise(resolve => {
      resolve(result);
      result = [];
    });
  }
};

获取签名

export const getRequestParam = async (
  browser: any,
  uid: string,
  deviceName: string
): Promise<any> => {
  return new Promise(async resolve => {
    try {
      const blockTypes = new Set(['image', 'media', 'font']);
      let data = {};
      const page = await browser.newPage();
      await page.emulate(devices[`${deviceName}`]);
      await page.setRequestInterception(true);
      page.goto(`https://www.iesdouyin.com/share/user/${uid}`);
      // 拦截请求获取请求参数
      page.on('request', async (interceptedRequest: any) => {
        const type = interceptedRequest.resourceType();
        const requestUrl = interceptedRequest.url();
        if (requestUrl.indexOf(apiUrl) > -1) {
          const qs = querystring.parse(requestUrl.split('?')[1]);
          const {
            user_id,
            sec_uid,
            count,
            max_cursor,
            aid,
            _signature,
            dytk,
          } = qs;
          const data = {
            user_id,
            sec_uid,
            count,
            max_cursor,
            aid,
            _signature,
            dytk,
          };
          resolve(data);
        } else {
          const shouldBlock = blockTypes.has(type);
          shouldBlock
            ? interceptedRequest.abort()
            : interceptedRequest.continue();
        }
      });
      page.on('load', async () => await page.close());
      // 1s后无反应就结束
      setTimeout(() => {
        resolve();
      }, 1000);
      // 进入页面
    } catch (e) {
      console.log(`---${uid}---getRequestParam:`, e);
      resolve();
    }
  });

# 总结

请求的人多且频繁,反扒措施做的越来越厉害,各种意向不到都有可能,nodejs需要写很多的异常处理代码及解决方案

#抖音
上次更新: 2021/12/19, 18:05:42
如何根据抖音号获取用户信息
模块化-import和require的区别

← 如何根据抖音号获取用户信息 模块化-import和require的区别→

最近更新
01
多分支修复撞车的问题
05-01
02
如何成为架构师
01-23
03
服务器部署全过程
11-23
更多文章>
Theme by Vdoing | Copyright © 2019-2026 fangdown | 粤ICP备19079809号
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式