5

2022寒假-王天睿-JuziBot项目开发-期中报告-智能信息助理

 2 years ago
source link: https://wechaty.js.org/2022/02/17/second-intern-juzibot-report/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

项目参与人

  • 导师:段清华
  • 学生:王天睿
  1. 01/13-01/17 : 成功运行dingdong bot,运行juzibot 初步了解juzibot 模块 并学习nodejs
  2. 01/18-01/25 : 开始根据现有功能书写测试用例,同时深入了解juzibot对应模块功能
  3. 01/26-02/11 : 现有功能的完善与测试用例的持续编写
  4. 02/12-02/22 : 抓取的微信问答信息,并完成数据清洗
  5. 02/23-02/27 : 开发句子秒回对应问答机器人,完成微信问答的api接入句子秒回
  • 测试模块编写
    • 文字向量服务 ✅
    • 语音转文字服务 ✅
    • 标签功能测试 ✅
    • 意图测试 ✅
    • 删除测试 ✅
    • 文件测试 ✅
    • 文件删除测试 ✅
    • 关键字测试 ✅
    • 我的文件测试 ✅
    • 重启测试 ✅
  • 小橘子🍊接口编写
    • 利用 SentenceTransformers 编写 句子->向量 API 替换 CPM ✅
    • 测试框架 并完成编写 语音->文字 服务 API ✅
    • 编写上述 API 测试代码并完成 docker 封装 ✅
  • 数据爬取
    • 微信广告帮助中心 ✅
    • 腾讯广告帮助中心 ✅
    • 企业微信帮助中心 ✅

遇到的问题及解决方案

使用不熟悉的nodejs语言完成任务

  • 遇到问题首先去积极的查阅资料,再进行多种尝试,然后询问老师,同时在解决问题后记录问题, 在此过程中学会并实现了相关编写,也提高了规范代码的意识,感谢老师的耐心帮助。

xiaojuzi 与 fastapi 交互

  • 怎样将 wechaty 接收到的 文字/图片/语音 与 fastapi 进行交互 , 确保它的传输高效,可以恢复。我去尝试构建请求,又没有较好的接受工具用以确定是否发送了理想的数据。所以我不得不先从 fastapi 端编写开始, 通过多次尝试编写如下

    python3

      class Item_audio(BaseModel):
          lol: Optional[str] = None
          audio_name: Optional[str] = None
          audio_data: Optional[str] = None
        
      @app.post("/api/audio/")
      async def create_item(item: Item_audio):
       
      gg=item.audio_data.replace("data:audio/silk;base64,","")
      audio_data = base64.b64decode(str(gg))
        
    

    nodejs

      const audioFileBox = await msg.toFileBox()
          const audio_dir = audioFileBox.name
          await audioFileBox.toFile(audioFileBox.name, true)
          const body = {
              audio_name: audioFileBox.name,
              audio_data: "data:audio/silk;base64," + fs.readFileSync(audio_dir, 'base64')
          };
          const response = await fetch(url + '/api/audio/', {
              body: JSON.stringify(body),
              method: 'post',
              headers: { 'Content-Type': 'application/json' }
          });
    

编写一个”透明”的Dockerfile 的小技巧

  • 编写 sts和asr api时候因为程序运行初次需要下载模型, 难以做到开箱即用,使用 COPY命令在dockerhub上显示一个不明文件移入 不够透明 容易给其他人使用造成困惑, 最后利用shell内的方式来实现

      RUN (echo "py code“) | python3 
    

编写爬虫的过程

  • 在编写xiaojuzi的语音转文字模块时候,学会了 nodejs 下 fetch的使用, 也对 post/get 请求有了一定认识, 编写爬虫的时候 因为界面是由javascript渲染得到的无法之间request网址,也就无法完成解析。

  • 起初我想用webscrapy插件抓取 但是效率很低 也不方便在没有GUI的vps上操作 还需要人点击显然不是理想的解决方案

  • 查阅资料后我决定选用 selenium 框架来渲染网页 然后使用 beautifulsoup lxml 进行爬取和解析 虽然可以通过 “–headless” 方式 在vps上 运行 但是为了确保 selenium 可以完整加载完一个界面 我需要每次都加上进五秒等待时间 这样就会导致爬取时间过长 而且本身我并不知道需要具体的抓取那些url

    selenium 配置

      from selenium import webdriver
      from selenium.webdriver.chrome.options import Options
    
      options = Options()
      options.headless = True
      options.add_argument("--window-size=1920x1080")
      # options.add_argument("window-size=1920x3000");
      options.add_argument("--disable-notifications")
    
      options.add_argument('--headless')
      options.add_argument('--disable-gpu')#谷歌文档提到需要加上这个属性避免使用gpu产生bug
        
      options.add_argument('disable-infobars')#隐藏"Chrome正在受到自动软件的控制"
      options.add_argument('lang=zh_CN.UTF-8')      # 设置中文
      options.add_argument('window-size=1920x3000') # 指定浏览器分辨率
      options.add_argument('--hide-scrollbars')     # 隐藏滚动条, 应对一些特殊页面
    
      driver = webdriver.Chrome("/Users/mac/Desktop/chromedriver", options=options)
    

    抓取 并 初步提取数据

      driver.get(url)
      time.sleep(8)
      print(driver.title)
      soup = BeautifulSoup(driver.page_source, 'lxml')
      if len(soup.find_all(attrs={'class': 'guide__title'})):            
          f.write(str(soup.find_all(attrs={'class': 'guide__title'})))
          f.write(",")
          f.write(url)
          f.write('\n')
      f.close()
    

    过程中可能会出现问题,这时候就可以打印 selenium 抓取时候的网页截图

      driver.get_screenshot_as_file('1.png')
    

    不稳定 , 而且低效

  • 爬虫的目的是抓取数据 既然难以渲染得到渲染界面 不如直接从数据请求分析的角度来寻找解决思路 , 于是就决定尝试抓包寻找解决方案. 首先通过 抓包的到 post 到数据 构造 curl请求 测试是否可以的到正确的响应。

    
      curl 'https://open.work.weixin.qq.com/help2/getQusList?lang=zh_CN&ajax=1&f=json&random=310321' \
          -H 'authority: open.work.weixin.qq.com' \
          -H 'sec-ch-ua: " Not A;Brand";v="99", "Chromium";v="98", "Microsoft Edge";v="98"' \
          -H 'accept: application/json, text/plain, */*' \
          -H 'content-type: application/json' \
          -H 'sec-ch-ua-mobile: ?0' \
          -H 'user-agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/98.0.4758.82 Safari/537.36 Edg/98.0.1108.51' \
          -H 'sec-ch-ua-platform: "macOS"' \
          -H 'origin: https://open.work.weixin.qq.com' \
          -H 'sec-fetch-site: same-origin' \
          -H 'sec-fetch-mode: cors' \
          -H 'sec-fetch-dest: empty' \
          -H 'referer: https://open.work.weixin.qq.com/help2/pc/15405?person_id=1' \
          -H 'accept-language: zh-TW,zh-CN;q=0.9,zh;q=0.8,en;q=0.7,en-GB;q=0.6,en-US;q=0.5,zh-HK;q=0.4' \
          -H 'cookie: pgv_pvid=3686684000; pac_uid=0_3cbd911c9ce83; pgv_info=ssid=s2798911544; pgv_pvi=4654092288; pgv_si=s6420471808; wwrtx.ref=direct; wwrtx.c_gdpr=0; wwrtx.refid=413075501944711; __utma=114362329.896737070.1644833346.1644833346.1644833346.1; __utmc=114362329; __utmz=114362329.1644833346.1.1.utmcsr=(direct)|utmccn=(direct)|utmcmd=(none); Hm_lvt_f2ba645ba13636ba52b0234381f51cbc=1644833347; Hm_lpvt_f2ba645ba13636ba52b0234381f51cbc=1644833970; uin=o0508195902; skey=@sEUq3VJWl; RK=hA9xBYtEcN; ptcz=f4917937db9babf5c19203049471a3e4d45ea1fe7b770ef9ea93e47e33c0c0f5; wwrtx.i18n_lan=cht' \
          --data-raw '{"person_id":1,"doc_id":15405}' \
          --compressed
    

    这是比较杂乱复杂的,我尝试给它简化 删去对于获取数据而言不必要的部分,的到下面的方法

      #https://open.work.weixin.qq.com/help2/getQusList?lang=zh_CN&ajax=1&f=json&random=569866 这个是浏览器访问的网址 下面是实际的请求
      curl 'https://open.work.weixin.qq.com/help2/getQusList?lang=zh_CN&ajax=1&f=json&random=310321' \
      -H 'user-agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/98.0.4758.82 Safari/537.36 Edg/98.0.1108.51' \
      -H 'referer: https://open.work.weixin.qq.com/help2/pc/15405?person_id=1' \
      --data-raw '{"person_id":1,"doc_id":15405}' 
    

    为了确保可靠性, curl复制粘贴非常方便, 在另一台vps上也进行了测试,的确可以的到正确的数据返回,下面就是把它转换成 requests 的请求 融入到代码里。

      headers = {
      'user-agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/98.0.4758.82 Safari/537.36 Edg/98.0.1108.51',
      'referer': 'https://open.work.weixin.qq.com/help2/pc/15405?person_id=1',
      }
    
      params = (
          ('lang', 'zh_CN'),
          ('ajax', '1'),
          ('f', 'json'),
          ('random', '310321'),
      )
    
      data = '{"person_id":1,"doc_id":15405}'
    

    此时就可以很清晰的看到请求的结构,大概率可以猜到 要么这个 random 是个假的 random 实际上是做文件索引的, 要么它是个真的random 做防止爬虫检测用,而我们要的对应文档存储就为 doc_id 。 通过脚本测试一下,很快就确定了 doc_id 对应的真的是doc 而random也是真的随机 😂😂😂😂😂 于是 我们可以在最简的基础上再反加回之前的参数 作用是伪装的更真实 防止被ban 得到下边最终构造代码

      import requests
      headers = {
          'authority': 'open.work.weixin.qq.com',
          'sec-ch-ua': '" Not A;Brand";v="99", "Chromium";v="98", "Microsoft Edge";v="98"',
          'accept': 'application/json, text/plain, */*',
          'content-type': 'application/json',
          'sec-ch-ua-mobile': '?0',
          'user-agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/98.0.4758.82 Safari/537.36 Edg/98.0.1108.51',
          'sec-ch-ua-platform': '"macOS"',
          'origin': 'https://open.work.weixin.qq.com',
          'sec-fetch-site': 'same-origin',
          'sec-fetch-mode': 'cors',
          'sec-fetch-dest': 'empty',
          'referer': 'https://open.work.weixin.qq.com/help2/pc/15405?person_id=1',
          'accept-language': 'zh-TW,zh-CN;q=0.9,zh;q=0.8,en;q=0.7,en-GB;q=0.6,en-US;q=0.5,zh-HK;q=0.4',
          'cookie': 'pgv_pvid=3686684000; pac_uid=0_3cbd911c9ce83; pgv_info=ssid=s2798911544; pgv_pvi=4654092288; pgv_si=s6420471808; wwrtx.ref=direct; wwrtx.c_gdpr=0; wwrtx.refid=413075501944711; __utma=114362329.896737070.1644833346.1644833346.1644833346.1; __utmc=114362329; __utmz=114362329.1644833346.1.1.utmcsr=(direct)|utmccn=(direct)|utmcmd=(none); Hm_lvt_f2ba645ba13636ba52b0234381f51cbc=1644833347; Hm_lpvt_f2ba645ba13636ba52b0234381f51cbc=1644833970; uin=o0508195902; skey=@sEUq3VJWl; RK=hA9xBYtEcN; ptcz=f4917937db9babf5c19203049471a3e4d45ea1fe7b770ef9ea93e47e33c0c0f5; wwrtx.i18n_lan=cht',
      }
    
      params = (
          ('lang', 'zh_CN'),
          ('ajax', '1'),
          ('f', 'json'),
          ('random', '22340'),
      )
    
      for num in range(begin,end):
          ## num 编号为主文档对应序号
          data = '{"person_id":1,"doc_id":%s}' % str(num)
    
          res = requests.post('https://open.work.weixin.qq.com/help2/getQusList', headers=headers, params=params, data=data)
    
    

    这样就可以很方便的遍历了,但是 我们不知道到底哪些URL才有我们想要的信息,于是就需要进行暴力遍历, 这时候 python 的异常处理模块变得非常适用 ,我们可以判断 是否返回值正常 若异常直接请求下一个即可, 然后将正确的数据利用pandas直接整理成csv

          try:
              Q = res['data']['helpdocument']['qusList'][0]['title']
              A_md = res['data']['helpdocument']['qusList'][0]['content_md']
              df = pd.DataFrame(data=[
                      [Q,A_md]],
                      columns = ['Q','A'],
                      )
              df.to_csv('data_all_in_one/QA_7k.csv', mode='a', header=False)
                
          except:
              continue
    

    此时 基本的部分已经完成, 但是逐个遍历速度还是太慢 于是融入了多线程

      def request_api(T_name,begin,end):
          xxx
      try:
          begin = 13000
          # 15000-16000 1k  # 16000-17000 2k # 17000-18000 3k # 18000-19000 4k
          # 19000-20000 5k # 14000-15000 6k $ 13000-14000 7k
          end = 14000
          sum = end - begin
          step = 4
          time = int(sum / step)
    
          ## 这里可以循环 但是没必要 
          _thread.start_new_thread(request_api, ("Thread-1", 0+begin , begin+time) )
          _thread.start_new_thread(request_api, ("Thread-2", begin + time , begin + time*2) )
          _thread.start_new_thread(request_api, ("Thread-3", begin + time*2 , begin + time*3) ) 
          _thread.start_new_thread(request_api, ("Thread-4", begin + time*3,  begin + time*4) )
      except:
          print ("Error: 无法启动线程")
    

    此时一个多线程的爬虫就实现了 , 相比 selenium 方法 速度快了数十倍 ,数据干净的多, 而且代码非常简洁 使用时候修改 begin end 就可以啦。 长时间爬取容易被封,所以就没有给它做成全自动的。 爬去完数据 最好检测一下 csv。

    分段爬取并整理成csv后 可以简单的用pandas再次清洗并合并的到最总数据

    另外两个网站爬去的思路也是类似的,不同的是数据初步清洗的过程不一样,有趣的是,微信广告帮助中心 请求后直接下载包含所有信息的json 在浏览器里面访问时候转圈 显示出来后点击抓包 是无法抓取的 因为根本就没有 请求数据的请求 只有统计点击的请求 所有的数据都在加载时候下载完啦 所以只需要

      url = 'https://ad.weixin.qq.com/openapi/acms_files/get?filename=data'
      rec = requests.get(url)
      f = open('data_all.txt','w')
      f.write(rec.text)
      f.close()
    

    再用 json 提取 和 pandas 清洗就ok了

Author: @tianrking Code: @tianrking/juzibot


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK