home.vue 71 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259
  1. <template>
  2. <div>
  3. <el-container style="height: 100vh;">
  4. <el-header height="45px" style="background-color: #fafafa; padding: 0 10px;">
  5. <soft-header ref="headerRef" @update-soft="updateSoft()" @login-url="loginUrl" @clear-cache="clearCache"></soft-header>
  6. </el-header>
  7. <el-main ref="el-main" style="background-color: #fafafa;">
  8. <template>
  9. <div style="padding: 10px 20px 0 20px; height: 100%;">
  10. <el-row type="flex" align="middle" justify="space-between">
  11. <div>
  12. <span>链接平台:</span>
  13. <el-radio-group v-model="menuIndex" size="mini" style="margin-right: 20px;">
  14. <!-- <el-radio-button label="3" size="mini">天猫</el-radio-button> -->
  15. <el-radio-button label="4" size="mini">淘宝/天猫</el-radio-button>
  16. <el-radio-button label="1" size="mini">1688</el-radio-button>
  17. <el-radio-button label="2" size="mini">京东</el-radio-button>
  18. <el-radio-button label="5" size="mini">小红书</el-radio-button>
  19. <el-radio-button label="10" size="mini">其他</el-radio-button>
  20. </el-radio-group>
  21. </div>
  22. <!-- 阿里巴巴账号 -->
  23. <template v-if="menuIndex == '1'">
  24. <div>
  25. <el-tag type="info" size="mini" v-if="alibabaStatus == 1">未检测</el-tag>
  26. <el-tag type="success" size="mini" v-if="alibabaStatus == 2">阿里巴巴账号已登录</el-tag>
  27. <el-link type="danger" style="text-decoration: underline;" v-if="alibabaStatus == 3" :underline="false" @click="loginUrl('https://www.1688.com')">未登录,点击登录阿里巴巴账号</el-link>
  28. <el-button size="mini" type="warning" :loading="checkLoading" style="margin-left: 10px;" :disabled='alibabaStatus == 2' @click="checkAlibabaLogin">检测登录状态</el-button>
  29. </div>
  30. </template>
  31. <!-- 天猫/淘宝 -->
  32. <template v-if="menuIndex == '3' || menuIndex == '4'">
  33. <div>
  34. <el-tag type="info" size="mini" v-if="tbStatus == 1">未检测</el-tag>
  35. <el-tag type="success" size="mini" v-if="tbStatus == 2">{{menuIndex == '3' ? '天猫' : '淘宝'}}账号已登录</el-tag>
  36. <el-link type="danger" style="text-decoration: underline;" v-if="tbStatus == 3" :underline="false" @click="loginUrl('https://login.taobao.com')">未登录,点击登录<span>{{menuIndex == '3' ? '天猫' : '淘宝'}}</span>账号</el-link>
  37. <el-button size="mini" type="warning" :loading="checkLoading" style="margin-left: 10px;" :disabled='tbStatus == 2' @click="checkLogin">检测登录状态</el-button>
  38. </div>
  39. </template>
  40. <!-- 京东账号 -->
  41. <template v-if="menuIndex == '2'">
  42. <div>
  43. <el-tag type="info" size="mini" v-if="jdStatus == 1">未检测</el-tag>
  44. <el-tag type="success" size="mini" v-if="jdStatus == 2">京东账号已登录</el-tag>
  45. <el-link type="danger" style="text-decoration: underline;" v-if="jdStatus == 3" :underline="false" @click="loginUrl('https://passport.jd.com/new/login.aspx')">未登录,点击登录京东账号</el-link>
  46. <el-button size="mini" type="warning" :loading="checkLoading" style="margin-left: 10px;" :disabled='jdStatus == 2' @click="checkJdLogin">检测登录状态</el-button>
  47. </div>
  48. </template>
  49. <!-- 小红书 -->
  50. <template v-if="menuIndex == '5'">
  51. <div>
  52. <el-tag type="info" size="mini" v-if="redStatus == 1">未检测</el-tag>
  53. <el-tag type="success" size="mini" v-if="redStatus == 2">小红书账号已登录</el-tag>
  54. <el-link type="danger" style="text-decoration: underline;" v-if="redStatus == 3" :underline="false" @click="loginUrl('https://www.xiaohongshu.com')">未登录,点击登录小红书账号</el-link>
  55. <el-button size="mini" type="warning" :loading="checkLoading" style="margin-left: 10px;" :disabled='redStatus == 2' @click="checkRedLogin">检测登录状态</el-button>
  56. </div>
  57. </template>
  58. <!-- 其他 -->
  59. <template v-if="menuIndex == '10'">
  60. <el-link type="danger" :underline="false" style="font-size: 12px;font-weight: 600;">其他平台仅提供试用,非会员功能</el-link>
  61. </template>
  62. </el-row>
  63. <el-row style="margin: 10px 0;">
  64. 提取模式:
  65. <el-radio-group v-model="exeType">
  66. <el-radio :label="1">后台运行</el-radio>
  67. <el-radio :label="2">前台显示</el-radio>
  68. </el-radio-group>
  69. </el-row>
  70. <el-row>
  71. <span class="set-title">保存目录:</span>
  72. <el-input :title="downloadDir" ref="upload-input" size="mini" @focus="pickPath" placeholder="请选择输出目录" v-model="downloadDir" readonly style="width:300px;" prefix-icon="el-icon-folder"></el-input>
  73. <el-popover placement="bottom" popper-class="popper-open" trigger="hover" content="打开保存目录">
  74. <i class="el-icon-folder-opened" slot="reference" style="padding-left: 5px; cursor: pointer; font-size: 22px; vertical-align: middle;" @click="openFolder()"></i>
  75. </el-popover>
  76. </el-row>
  77. <div style="margin-top: 10px;">
  78. <el-input id="aaa" type="textarea" :rows="3" placeholder="请输入需要提取的网址" v-model="formatUrl"></el-input>
  79. <div class="content-top" style="padding: 10px 0; text-align: center; display: inherit;">
  80. <el-button size="small" type="danger" @click="addLinks()" :loading="addLoading">开始提取</el-button>
  81. <el-button size="small" type="danger" @click="pauseLinks()">停止提取</el-button>
  82. <el-button size="small" type="danger" @click="clearList()">清除结果</el-button>
  83. <el-button size="small" type="danger" @click="exportLinks()">合并导出全部</el-button>
  84. </div>
  85. </div>
  86. <div class="table-scroll" style="height: calc(100% - 290px); overflow: hidden;">
  87. <el-row type="flex" justify="space-between">
  88. <div>
  89. <h4 style="display: inline-block;">
  90. 提取结果:
  91. </h4>
  92. </div>
  93. </el-row>
  94. <div style="display: flex; flex-wrap: nowrap;justify-content: space-between;height: calc(100% - 60px);">
  95. <vxe-table style="width: 100%;" ref="xTable" show-overflow class="img-table" max-height="100%" empty-text="没有更多数据了!" :loading="tabLoading" :row-config="{isHover: true}"
  96. :loading-config="{icon: 'vxe-icon-indicator roll', text: '加载中...'}" :data="this[listStr+'List']" :scroll-y="{enabled: true}">
  97. <vxe-column field="title" title="名称"></vxe-column>
  98. <vxe-column field="page" title="页数">
  99. <template #default="{ row }">
  100. 第{{row.currentPage}}页,共{{row.totalPage}}页
  101. </template>
  102. </vxe-column>
  103. <vxe-column field="filesize" title="条数" width="100">
  104. <template #default="{ row }">
  105. <span v-if="row.urls">{{row.urls.length}}</span>
  106. </template>
  107. </vxe-column>
  108. <vxe-column field="status" title="状态" width="150">
  109. <template #default="{ row }">
  110. <template v-if="row.status == '1'">
  111. <i class="el-icon-info" style="font-size: 16px; color: #999;"></i>
  112. <span>待操作</span>
  113. </template>
  114. <template v-if="row.status == '2'">
  115. <i class="el-icon-success" style="font-size: 16px; color: #19be6b;"></i>
  116. <span>导出成功</span>
  117. </template>
  118. </template>
  119. </vxe-column>
  120. <vxe-column title="操作" width="150">
  121. <template #default="{ row, rowIndex }">
  122. <el-button :loading="row.loading" type="danger" size="mini" plain @click="exportPageLinks(row)">导出</el-button>
  123. </template>
  124. </vxe-column>
  125. </vxe-table>
  126. </div>
  127. </div>
  128. </div>
  129. </template>
  130. </el-main>
  131. <el-footer height="48px">
  132. <!-- 更新 -->
  133. <soft-update ref="updateRef" :showDowload="dowloadModel" :dowloadFinish="finishModel"></soft-update>
  134. </el-footer>
  135. </el-container>
  136. </div>
  137. </template>
  138. <script>
  139. import os from 'os'
  140. import fs from 'fs'
  141. import request from 'request'
  142. import path from 'path';
  143. import xlsx from 'node-xlsx';
  144. import softUpdate from './update.vue';
  145. import softHeader from './header.vue';
  146. import electronApi from '@/utils/electronApi';
  147. import pjson from '/package.json'
  148. // import puppeteer from 'puppeteer'
  149. import puppeteer from 'puppeteer-extra'
  150. const axios = require('axios');
  151. const StealthPlugin = require('puppeteer-extra-plugin-stealth');
  152. const { ipcRenderer } = require('electron');
  153. const listNameArr = ['alibaba','jd','tmall','tb','red','aliguoji','acaigou','amazon', 'pdd' ,'common','goofish','dy'];
  154. let separator = '';
  155. if (os.platform == 'linux') {
  156. separator = '/'
  157. } else {
  158. separator = '\\'
  159. }
  160. export default {
  161. name: 'landing-page',
  162. components: {
  163. softUpdate,
  164. softHeader
  165. },
  166. data() {
  167. return {
  168. menuIndex: '4',
  169. exeType: 1,
  170. usageTimes: 3,
  171. tipsModal: false,
  172. tipsDesc: "暂无下载权限,请开通VIP会员使用",
  173. formatUrl: '',
  174. tabLoading: false,
  175. productName: pjson.softInfo.softName,
  176. imgUrl: this.$api.imgUrl,
  177. downloadDir: os.userInfo().homedir + separator + "Downloads",
  178. dowloadModel: false,
  179. finishModel: false,
  180. loading: false,
  181. tbList: [], //淘宝数据
  182. tmallList: [],
  183. alibabaList: [],
  184. jdList: [],
  185. redList: [],
  186. commonList: [],
  187. execNum: 100, //限制
  188. checkLoading: false, //点击检测登录状态
  189. tbStatus: 1, // 1、未检测 2、已经登录 3、未登录
  190. jdStatus: 1, // 1、未检测 2、已经登录 3、未登录
  191. redStatus: 1, //同上
  192. alibabaStatus: 1, //同上上
  193. /** 浏览器名称 **/
  194. alibabaBrowser: null,
  195. tbBrowser: null,
  196. jdBrowser: null,
  197. commonBrowser: null,
  198. redBrowser: null,
  199. loginBrowser: null, // 登录用的浏览器实例
  200. pauseFlag: true, //暂停中止标志
  201. addLoading: false,
  202. videoBrowser: null,
  203. parseLoading: false,
  204. mergeLoading: false
  205. };
  206. },
  207. computed: {
  208. listStr: function(){
  209. let index = Number(this.menuIndex) - 1;
  210. return listNameArr[index];
  211. }
  212. },
  213. async mounted() {
  214. this.$refs.updateRef.updateSoft(true);
  215. let homedir = os.userInfo().homedir;
  216. if (fs.existsSync(homedir + separator + "Desktop")) {
  217. this.downloadDir = homedir + separator + "Desktop"
  218. } else {
  219. this.downloadDir = homedir + separator + "Downloads"
  220. }
  221. let dir = this.$utils.getStorage('downloadDir');
  222. if(dir && fs.existsSync(dir)){
  223. this.downloadDir = dir;
  224. }
  225. if (!fs.existsSync(this.downloadDir + separator + pjson.softInfo.softName)) {
  226. fs.mkdirSync(this.downloadDir + separator + pjson.softInfo.softName);
  227. }
  228. if (!fs.existsSync(os.tmpdir() + separator + 'chrome-data-capture')) {
  229. fs.mkdirSync(os.tmpdir() + separator + 'chrome-data-capture');
  230. }
  231. if (!fs.existsSync(os.tmpdir() + separator + 'chrome-data-capture-jd')) {
  232. fs.mkdirSync(os.tmpdir() + separator + 'chrome-data-capture-jd');
  233. }
  234. if (!fs.existsSync(os.tmpdir() + separator + 'chrome-data-capture-local')) {
  235. fs.mkdirSync(os.tmpdir() + separator + 'chrome-data-capture-local');
  236. }
  237. // 打开浏览器
  238. const {
  239. shell
  240. } = require('electron');
  241. const links = document.querySelectorAll('a[href]');
  242. links.forEach(link => {
  243. link.addEventListener('click', e => {
  244. const url = link.getAttribute('href');
  245. e.preventDefault();
  246. shell.openExternal(url);
  247. });
  248. });
  249. // 判断系统版本 低于10使用兼容模式
  250. if(os.release() && os.release().indexOf('.') > -1){
  251. let systemVersion = Number(os.release().split('.')[0]);
  252. if(systemVersion < 10){ // 低于win10,软件打开时候浏览器设置为兼容版
  253. this.$utils.setStorage('versionType', 1);
  254. }else{
  255. this.$utils.setStorage('versionType', 2);
  256. }
  257. }
  258. // 初始化开发者设置
  259. this.$utils.setStorage('waitUntil', 'networkidle2');
  260. document.getElementById('aaa').addEventListener('contextmenu', e => {
  261. e.preventDefault();
  262. // 发送事件到主进程,附带元素类型信息
  263. ipcRenderer.send('show-context-menu', 'input');
  264. });
  265. },
  266. methods: {
  267. // 实时获取页面滚动加载时间
  268. initMs(){
  269. let pageMs = this.$utils.getStorage('pageMs');
  270. if(pageMs){
  271. return Number(pageMs);
  272. }else{
  273. return 600;
  274. }
  275. },
  276. // 实时获取开发者设置
  277. initDevelop(){
  278. let develop = {
  279. headless: true,
  280. waitUntil: 'networkidle2'
  281. };
  282. let n1 = this.exeType;
  283. let n2 = this.$utils.getStorage('waitUntil');
  284. if(n1){
  285. if(n1 == 1){
  286. develop.headless = true;
  287. }else if(n1 == 2){
  288. develop.headless = false;
  289. }
  290. }
  291. if(n2){
  292. develop.waitUntil = n2;
  293. }
  294. return develop;
  295. },
  296. // 实时获取浏览器路径
  297. initPath(){
  298. let chromeType = this.$utils.getStorage('chromeType');
  299. let chromePath = puppeteer.executablePath().replace('win32-1', 'win64-1');
  300. if(chromeType == 1){ // 电脑自带浏览器
  301. chromePath = this.$utils.getStorage('chromePath');
  302. }else{
  303. chromePath = puppeteer.executablePath().replace('win32-1', 'win64-1');
  304. let versionType = this.$utils.getStorage('versionType');
  305. if(versionType && versionType == 1){
  306. chromePath = chromePath.replace('chrome-win', 'chrome7');
  307. }
  308. }
  309. return chromePath;
  310. },
  311. // 实时获取浏览器数据缓存
  312. initDataDir(tag){
  313. let chromeType = this.$utils.getStorage('chromeType');
  314. let userDataDir = os.tmpdir() + separator + 'chrome-data-capture';
  315. if(chromeType == 1){ // 电脑自带浏览器
  316. userDataDir = os.tmpdir() + separator + 'chrome-data-capture-local';
  317. }else{
  318. userDataDir = os.tmpdir() + separator + 'chrome-data-capture';
  319. if(tag == 'jd'){
  320. userDataDir = os.tmpdir() + separator + 'chrome-data-capture-jd';
  321. }
  322. }
  323. return userDataDir;
  324. },
  325. // 任务下载间隔时间
  326. initGap(){
  327. let gap = this.$utils.getStorage('gap');
  328. if(gap){
  329. return Number(gap);
  330. }else{
  331. return 1;
  332. }
  333. },
  334. // 通用的延迟函数
  335. async delay(t) {
  336. let s = this.initGap();
  337. if(t){
  338. s = t;
  339. }
  340. if(s > 0){
  341. return new Promise(resolve => setTimeout(resolve, Number(s)*1000));
  342. }else{
  343. return null;
  344. }
  345. },
  346. checkAuthority(){
  347. let authority = this.$refs.headerRef.authority;
  348. this.$refs.imgRef.authority = authority;
  349. },
  350. // 选择目录
  351. pickPath() {
  352. this.$refs['upload-input'].blur();
  353. electronApi.call('pickDir', []).then((path) => {
  354. if (path) {
  355. this.downloadDir = path;
  356. this.$utils.setStorage('downloadDir', path);
  357. }
  358. });
  359. },
  360. // 打开自定义下载目录
  361. openFolder() {
  362. let path = this.downloadDir;
  363. if (fs.existsSync(this.downloadDir + separator + pjson.softInfo.softName)) {
  364. path = this.downloadDir + separator + pjson.softInfo.softName;
  365. } else {
  366. fs.mkdirSync(this.downloadDir + separator + pjson.softInfo.softName);
  367. path = this.downloadDir + separator + pjson.softInfo.softName;
  368. }
  369. electronApi.call('showItemInfolder', [path + '\\tty.tty'])
  370. },
  371. openVip() {
  372. this.tipsModal = false;
  373. this.$refs.headerRef.openVip();
  374. },
  375. updateSoft() {
  376. this.$refs.updateRef.updateSoft();
  377. },
  378. // 清除缓存的后续操作
  379. async clearCache(){
  380. if(this.loginBrowser){
  381. await this.loginBrowser.close();
  382. this.loginBrowser = null;
  383. }
  384. if(this.videoBrowser){
  385. await this.videoBrowser.close();
  386. this.videoBrowser = null;
  387. }
  388. },
  389. // 清除结果
  390. clearList(){
  391. let index = Number(this.menuIndex) - 1;
  392. this[listNameArr[index]+'List'] = [];
  393. },
  394. //停止提取
  395. async pauseLinks(){
  396. this.addLoading = false;
  397. let index = Number(this.menuIndex) - 1;
  398. let browserName = listNameArr[index] + 'Browser';
  399. if(index == '2'){ // 天猫和淘宝共用一个
  400. browserName = 'tbBrowser';
  401. }
  402. if(this[browserName]){ //关闭浏览器
  403. await this[browserName].close();
  404. this[browserName] = null;
  405. }
  406. },
  407. //导出全部链接
  408. exportLinks(){
  409. let allRows = {
  410. title: '淘宝商品链接汇总',
  411. urls: [],
  412. loading: false,
  413. status: '1'
  414. };
  415. switch(this.menuIndex){
  416. case '1': // 阿里巴巴
  417. allRows.title = '阿里巴巴商品链接汇总';
  418. break;
  419. case '2': // 京东
  420. allRows.title = '京东商品链接汇总';
  421. break;
  422. case '3': // 天猫
  423. case '4': // 淘宝
  424. let allUrls = [];
  425. for(let i = 0; i < this.tbList.length; i++){
  426. allUrls = allUrls.concat(this.tbList[i].urls);
  427. }
  428. allRows.urls = allUrls;
  429. break;
  430. case '5': // 小红书
  431. allRows.title = '小红书笔记链接汇总';
  432. break;
  433. case '10': // 普通网址
  434. allRows.title = '网页链接汇总';
  435. break;
  436. }
  437. this.exportPageLinks(allRows);
  438. },
  439. // 导出单页链接
  440. exportPageLinks(row){
  441. row.loading = true;
  442. let filename = row.title + '-' + this.$utils.formatFileTime() +'.xlsx';
  443. let sheet1 = [['序号','商品ID','商品标题','商品链接','价格','销量','店铺名称','店铺地址','首图地址']];
  444. if(this.menuIndex == '4'){
  445. sheet1 = [['序号','商品ID','商品标题','商品链接','价格','销量','店铺名称','店铺地址','首图地址','商品短链接']];
  446. }else if(this.menuIndex == '5'){
  447. sheet1 = [['序号','笔记标题','笔记链接','用户名','点赞量','发布时间','首图地址']];
  448. }else if(this.menuIndex == '10'){
  449. sheet1 = [['序号','标题','链接']];
  450. }
  451. for(let i = 0; i < row.urls.length; i++){
  452. let item = row.urls[i];
  453. let info = [i+1, item.id, item.title, item.href, item.price, item.realSales, item.shopNameText, item.shopUrl, item.mainPic];
  454. if(this.menuIndex == '4'){
  455. info = [i+1, item.id, item.title, item.href, item.price, item.realSales, item.shopNameText, item.shopUrl, item.mainPic, 'https://detail.tmall.com/item.htm?id='+item.id];
  456. }else if(this.menuIndex == '5'){
  457. info = [i+1, item.title, item.href, item.name, item.realSales, item.time, item.mainPic];
  458. }else if(this.menuIndex == '10'){
  459. info = [i+1, item.title, item.href];
  460. }
  461. sheet1.push(info);
  462. }
  463. let buffer = xlsx.build([
  464. {
  465. name: 'sheet1',
  466. data: sheet1
  467. }
  468. ]);
  469. if (!fs.existsSync(this.downloadDir + separator + pjson.softInfo.softName)) {
  470. fs.mkdirSync(this.downloadDir + separator + pjson.softInfo.softName);
  471. }
  472. fs.writeFileSync(this.downloadDir + separator + pjson.softInfo.softName + separator + filename, buffer, {'flag':'w'});
  473. row.loading = false;
  474. row.status = '2';
  475. this.$message({message: '文件导出成功!', type: 'success'});
  476. electronApi.call('showItemInfolder',[this.downloadDir + separator + pjson.softInfo.softName + separator + filename]);
  477. },
  478. //提取链接
  479. async addLinks(){
  480. this.addLoading = true;
  481. setTimeout(() => {
  482. this.addLoading = false;
  483. }, 30000);
  484. if(this.loginBrowser){
  485. await this.loginBrowser.close();
  486. this.loginBrowser = null;
  487. }
  488. let index = Number(this.menuIndex) - 1;
  489. if(this.menuIndex == '1'){ // 阿里巴巴
  490. if(this.alibabaStatus == 1 || this.alibabaStatus == 3){ // 未检测登录状态/或提示未登录状态,开始检测
  491. await this.checkAlibabaLogin().then((data) => {
  492. if(data != 2){ // 未登录
  493. this.alibabaStatus = 3;
  494. this.loginVisible = true;
  495. }
  496. }).catch(err => {
  497. this.alibabaStatus = 3;
  498. });
  499. if(this.alibabaStatus == 3){
  500. this.loading = false;
  501. return false;
  502. }
  503. }
  504. }
  505. if(this.menuIndex == '2'){ // 京东
  506. if(this.jdStatus == 1 || this.jdStatus == 3){ // 未检测登录状态/或提示未登录状态,开始检测
  507. await this.checkJdLogin().then((data) => {
  508. if(data != 2){ // 未登录
  509. this.jdStatus = 3;
  510. this.loginVisible = true;
  511. }
  512. }).catch(err => {
  513. this.jdStatus = 3;
  514. });
  515. if(this.jdStatus == 3){
  516. this.loading = false;
  517. return false;
  518. }
  519. }
  520. }
  521. if(this.menuIndex == '3' || this.menuIndex == '4'){ // 天猫/淘宝
  522. if(this.tbStatus == 1 || this.tbStatus == 3){ // 未检测登录状态/或提示未登录状态,开始检测
  523. await this.checkLogin().then((data) => {
  524. if(data != 2){ // 未登录
  525. this.tbStatus = 3;
  526. this.loginVisible = true;
  527. }
  528. }).catch(err => {
  529. this.tbStatus = 3;
  530. });
  531. }
  532. if(this.tbStatus == 3){
  533. this.loading = false;
  534. return false;
  535. }
  536. }
  537. if(this.menuIndex == '5'){ // 小红书
  538. if(this.redStatus == 1 || this.redStatus == 3){ // 未检测登录状态/或提示未登录状态,开始检测
  539. await this.checkRedLogin().then((data) => {
  540. if(data != 2){ // 未登录
  541. this.redStatus = 3;
  542. this.loginVisible = true;
  543. }
  544. }).catch(err => {
  545. this.redStatus = 3;
  546. });
  547. if(this.redStatus == 3){
  548. this.loading = false;
  549. return false;
  550. }
  551. }
  552. }
  553. let userDataDir = this.initDataDir();
  554. if(this.menuIndex == '2'){
  555. userDataDir = this.initDataDir('jd');
  556. }
  557. // 运行不同平台的浏览器
  558. puppeteer.use(StealthPlugin());
  559. let browserName = listNameArr[index] + 'Browser';
  560. if(index == '2'){ // 天猫和淘宝共用一个
  561. browserName = 'tbBrowser';
  562. }
  563. let headless = true;
  564. headless = this.initDevelop().headless;
  565. let redSize = '';
  566. if(['3','4','5'].indexOf(this.menuIndex) > -1){
  567. redSize = '--window-size=1920,800'; //给浏览器一个初始大小,在无头模式下,页面会自适用缩放
  568. }
  569. try{
  570. this[browserName] = await puppeteer.launch({
  571. headless: headless,
  572. executablePath: this.initPath(),
  573. userDataDir: userDataDir,
  574. args: [
  575. '--start-maximized',
  576. '--no-sandbox',
  577. '--disable-setuid-sandbox',
  578. '--disable-blink-features=AutomationControlled',
  579. redSize
  580. ]
  581. });
  582. switch(this.menuIndex){
  583. case '1': // 阿里巴巴
  584. this.alibabaScanUrl(this.formatUrl);
  585. break;
  586. case '2': // 京东
  587. this.jdScanUrl(this.formatUrl);
  588. break;
  589. case '3': // 天猫
  590. case '4': // 淘宝
  591. this.tbScanUrl(this.formatUrl);
  592. break;
  593. case '5': // 小红书
  594. this.redScanUrl(this.formatUrl);
  595. break;
  596. case '10': // 普通网址
  597. this.commonScanUrl(this.formatUrl);
  598. break;
  599. }
  600. }catch(e){
  601. console.log(e, '测试');
  602. }
  603. },
  604. // 天猫/淘宝扫描网址
  605. tbScanUrl(url){
  606. (async () => {
  607. try{
  608. let authority = this.$refs.headerRef.authority.isAuthority;
  609. let page = await this.tbBrowser.newPage();
  610. let responseVideo = [];
  611. let waitUntil = 'networkidle2';
  612. waitUntil = this.initDevelop().waitUntil;
  613. await page.goto(url, {waitUntil : waitUntil});
  614. let searchTitle = await page.title() || '淘宝商品搜索';
  615. if(searchTitle){
  616. searchTitle = searchTitle.substring(0, 50);
  617. if(this.containsAnyChar(searchTitle)){ //判断是否含有特殊字符
  618. searchTitle = searchTitle.replace(/[\\/:*?"<>|#%&\s]/g, "");
  619. }
  620. }
  621. let outObj = {};
  622. let pageHandle = await page.$$('[class^=next-pagination-display]');
  623. let currentPage = 1;
  624. let totalPage = 1;
  625. if(pageHandle && pageHandle.length > 0){
  626. let pageValue = await page.$eval('[class^=next-pagination-display]', el => el.innerText);
  627. currentPage = pageValue.replace('\n', '').split('/')[0] || 1;
  628. totalPage = pageValue.replace('\n', '').split('/')[1] || 1;
  629. }
  630. for(let i = Number(currentPage); i < Number(totalPage)+1; i++){
  631. let pValue = i + 1;
  632. let pageHandle = await page.$$('.next-pagination-jump-input input');
  633. if(pageHandle && pageHandle.length > 0){ // 页码多,可以点击跳转制定页码
  634. await page.type('.next-pagination-jump-input input', pValue.toString());
  635. }
  636. let itemTitle = searchTitle + '-' + i;
  637. const imgInfo = await page.evaluate((authority, execNum, itemTitle) => {
  638. let outObj = {
  639. title: itemTitle,
  640. currentPage: 1,
  641. totalPage: 1,
  642. urls: [],
  643. status: '1',
  644. loading: false
  645. };
  646. let total = document.querySelector('[class^=next-pagination-display]');
  647. if(total){
  648. outObj.currentPage = total.innerText.replace('\n', '').split('/')[0];
  649. outObj.totalPage = total.innerText.replace('\n', '').split('/')[1];
  650. }
  651. //主图
  652. let arr1 = document.querySelectorAll('a[id*=item_id]');
  653. for(let i=0; i< arr1.length; i++){
  654. let href = arr1[i].href;
  655. let id = arr1[i].id.replace('item_id_', '');
  656. let title = arr1[i].querySelector('[class^=title]') ? arr1[i].querySelector('[class^=title]').innerText : '淘宝商品'+new Date().getTime();
  657. let priceInt = arr1[i].querySelector('[class^=priceInt]') ? arr1[i].querySelector('[class^=priceInt]').innerText : 0;
  658. let priceFloat = arr1[i].querySelector('[class^=priceFloat]') ? arr1[i].querySelector('[class^=priceFloat]').innerText : '';
  659. let price = priceInt + priceFloat;
  660. let realSales = arr1[i].querySelector('[class^=realSales]') ? arr1[i].querySelector('[class^=realSales]').innerText : '';
  661. let shopNameText = arr1[i].querySelector('[class^=shopNameText]') ? arr1[i].querySelector('[class^=shopNameText]').innerText : '';
  662. let shopUrl = arr1[i].querySelector('a[class^=shopName]') ? arr1[i].querySelector('a[class^=shopName]').href : '';
  663. let mainPic = arr1[i].querySelector('img[class^=mainImg]') ? arr1[i].querySelector('img[class^=mainImg]').src : '';
  664. let info = {
  665. id: id,
  666. title: title,
  667. href: href,
  668. price: price,
  669. realSales: realSales,
  670. shopNameText: shopNameText,
  671. shopUrl: shopUrl,
  672. mainPic: mainPic
  673. }
  674. outObj.urls.push(info);
  675. }
  676. //page.$$('.next-pagination-jump-input input');
  677. // let skuHandle = document.querySelector('.next-pagination-jump-go');
  678. // if(skuHandle){
  679. // skuHandle.click({force: true});
  680. // }
  681. let nextHandle = document.querySelector('button[class*=next-next]');
  682. if(nextHandle){
  683. nextHandle.click({force: true});
  684. }
  685. return outObj;
  686. }, authority, this.execNum, itemTitle);
  687. this.tbList.push(imgInfo);
  688. await this.delay(5);
  689. }
  690. await this.tbBrowser.close();
  691. this.addLoading = false;
  692. }catch(e){
  693. this.addLoading = false;
  694. this.showError(e);
  695. }
  696. })();
  697. },
  698. // 阿里巴巴扫描网址
  699. alibabaScanUrl(url){
  700. (async () => {
  701. try{
  702. let authority = this.$refs.headerRef.authority.isAuthority;
  703. let page = await this.alibabaBrowser.newPage();
  704. let responseVideo = [];
  705. let waitUntil = 'networkidle2';
  706. waitUntil = this.initDevelop().waitUntil;
  707. await page.goto(url, {waitUntil : waitUntil});
  708. let searchTitle = await page.title() || '阿里巴巴商品搜索';
  709. if(searchTitle){
  710. searchTitle = searchTitle.substring(0, 50);
  711. if(this.containsAnyChar(searchTitle)){ //判断是否含有特殊字符
  712. searchTitle = searchTitle.replace(/[\\/:*?"<>|#%&\s]/g, "");
  713. }
  714. }
  715. //找页码信息
  716. let outObj = {};
  717. let pageHandle = await page.$$('[class^=pagination-container] span');
  718. let currentPage = 1;
  719. let totalPage = 1;
  720. if(pageHandle && pageHandle.length > 0){
  721. totalPage = await page.$eval('[class^=pagination-container] span', el => el.innerText);
  722. currentPage = await page.$eval('[class^=pagination-container] .fui-current', el => el.innerText);
  723. }
  724. for(let i = Number(currentPage); i < Number(totalPage)+1; i++){
  725. await new Promise(resolve => setTimeout(resolve, 1500)).then(async() => {
  726. await page.evaluate(() => {
  727. window.scrollTo({
  728. top: 10000,
  729. behavior: "smooth"
  730. });
  731. });
  732. });
  733. await new Promise(resolve => setTimeout(resolve, 5000)).then(async() => {
  734. let itemTitle = searchTitle + '-' + i;
  735. const imgInfo = await page.evaluate((authority, execNum, itemTitle) => {
  736. let outObj = {
  737. title: itemTitle,
  738. currentPage: 1,
  739. totalPage: 1,
  740. urls: [],
  741. status: '1',
  742. loading: false
  743. };
  744. let total = document.querySelector('[class^=pagination-container] span');
  745. if(total){
  746. outObj.currentPage = document.querySelector('[class^=pagination-container] .fui-current').innerText || 1;
  747. outObj.totalPage = total.innerText || 1;
  748. }
  749. // 广告链接
  750. // document.querySelectorAll('[class*=space-common-offerlist] a[class*=pc-ad]');
  751. // 正常
  752. let arr1 = document.querySelectorAll('[class*=space-common-offerlist] a[class*=search-offer-item]');
  753. for(let i=0; i< arr1.length; i++){
  754. let href = arr1[i].href;
  755. let idData = arr1[i].dataset.renderkey || '';
  756. let length = idData.split('_').length - 1;
  757. let id = idData.split('_')[length];
  758. let title = arr1[i].querySelector('[class^=title-text]') ? arr1[i].querySelector('[class^=title-text]').innerText : '阿里巴巴商品'+new Date().getTime();
  759. let priceText = arr1[i].querySelector('[class^=price-item]') ? arr1[i].querySelector('[class^=price-item]').innerText : '';
  760. let price = priceText.replace('¥', '').replaceAll('\n', '');
  761. let realDom = arr1[i].querySelector('.offer-price-row .offer-desc-item');
  762. let realSales = realDom ? realDom.innerText : '';
  763. let shopNameText = arr1[i].querySelector('[class^=offer-shop-row]') ? arr1[i].querySelector('[class^=offer-shop-row]').innerText : '';
  764. let shopUrl = arr1[i].querySelector('[class^=offer-shop-row] a') ? arr1[i].querySelector('[class^=offer-shop-row] a').href : '';
  765. let mainPic = arr1[i].querySelector('img[class^=main-img]') ? arr1[i].querySelector('img[class^=main-img]').src : '';
  766. let info = {
  767. id: id,
  768. title: title,
  769. href: href,
  770. price: price,
  771. realSales: realSales,
  772. shopNameText: shopNameText,
  773. shopUrl: shopUrl,
  774. mainPic: mainPic
  775. }
  776. outObj.urls.push(info);
  777. }
  778. let nextHandle = document.querySelector('.pagination-container .fui-next');
  779. if(nextHandle){
  780. nextHandle.click({force: true});
  781. }
  782. return outObj;
  783. }, authority, this.execNum, itemTitle);
  784. this.alibabaList.push(imgInfo);
  785. });
  786. }
  787. await this.alibabaBrowser.close();
  788. this.addLoading = false;
  789. }catch(e){
  790. this.addLoading = false;
  791. this.showError(e);
  792. }
  793. })();
  794. },
  795. // 京东扫描网址
  796. jdScanUrl(url){
  797. (async () => {
  798. try{
  799. let authority = this.$refs.headerRef.authority.isAuthority;
  800. let page = await this.jdBrowser.newPage();
  801. let responseVideo = [];
  802. let waitUntil = 'networkidle2';
  803. waitUntil = this.initDevelop().waitUntil;
  804. await page.goto(url, {waitUntil : waitUntil});
  805. let searchTitle = await page.title() || '京东商品搜索';
  806. if(searchTitle){
  807. searchTitle = searchTitle.substring(0, 50);
  808. if(this.containsAnyChar(searchTitle)){ //判断是否含有特殊字符
  809. searchTitle = searchTitle.replace(/[\\/:*?"<>|#%&\s]/g, "");
  810. }
  811. }
  812. //找页码信息
  813. let outObj = {};
  814. let pageHandle = await page.$$('[class*=quick-result]');
  815. let currentPage = 1;
  816. let totalPage = 1;
  817. if(pageHandle && pageHandle.length > 0){
  818. let pageContent = await page.$eval('[class*=quick-result]', el => el.innerText);
  819. pageContent = pageContent.replace('\n', '').replace('\n', '');
  820. currentPage =pageContent.split('/')[0];
  821. totalPage = pageContent.split('/')[1];
  822. }else{
  823. let pageHandle = await page.$$('[class*=pagination_center]');
  824. if(pageHandle && pageHandle.length > 0){
  825. currentPage = await page.$eval('[class*=pagination_center] [class*=active]', el => el.innerText);
  826. let totalInfo = await page.$eval('[class*=pagination_total]', el => el.innerText);
  827. totalPage = totalInfo.replace('共', '').replace('页', '');
  828. }
  829. }
  830. for(let i = Number(currentPage); i < Number(totalPage)+1; i++){
  831. await new Promise(resolve => setTimeout(resolve, 1500)).then(async() => {
  832. await page.evaluate(() => {
  833. window.scrollTo({
  834. top: 8000,
  835. behavior: "smooth"
  836. });
  837. });
  838. });
  839. await new Promise(resolve => setTimeout(resolve, 5000)).then(async() => {
  840. let itemTitle = searchTitle + '-' + i;
  841. const imgInfo = await page.evaluate((authority, execNum, itemTitle) => {
  842. let outObj = {
  843. title: itemTitle,
  844. currentPage: 1,
  845. totalPage: 1,
  846. urls: [],
  847. status: '1',
  848. loading: false
  849. };
  850. let total = document.querySelector('[class*=quick-result]');
  851. if(total){
  852. let pageContent = total.innerText.replace('\n', '').replace('\n', '');
  853. outObj.currentPage =pageContent.split('/')[0] || 1;
  854. outObj.totalPage = pageContent.split('/')[1] || 1;
  855. }else{
  856. let currentDom = document.querySelector('[class*=pagination_center] [class*=active]');
  857. if(currentDom){
  858. outObj.currentPage = currentDom.innerText;
  859. }
  860. let totalInfo = document.querySelector('[class*=pagination_total]');
  861. if(totalInfo){
  862. utObj.totalPage = totalInfo.innerText.replace('共', '').replace('页', '');
  863. }
  864. }
  865. // 正常
  866. let arr1 = document.querySelectorAll('[class*=goodsContainer]>div');
  867. for(let i=0; i< arr1.length; i++){
  868. let sku = arr1[i].dataset.sku || '';
  869. let title = arr1[i].querySelector('[class*=goods_title]') ? arr1[i].querySelector('[class*=goods_title]').innerText : '京东商品'+new Date().getTime();
  870. let priceText = arr1[i].querySelector('[class*=price]') ? arr1[i].querySelector('[class*=price]').innerText : '';
  871. let price = priceText.replace('¥', '').replaceAll('\n', '');
  872. let realDom = arr1[i].querySelector('[class*=goods_volume]');
  873. let realSales = realDom ? realDom.innerText : '';
  874. let shopNameText = arr1[i].querySelector('[class*=shopFloor]') ? arr1[i].querySelector('[class*=shopFloor]').innerText : '';
  875. let shopUrl = arr1[i].querySelector('[class*=shopFloor] a') ? arr1[i].querySelector('[class*=shopFloor] a').href : '';
  876. let mainPic = arr1[i].querySelector('img') ? arr1[i].querySelector('img').src : '';
  877. let info = {
  878. title: title,
  879. id: sku,
  880. href: "https://item.jd.com/" + sku + ".html",
  881. price: price,
  882. realSales: realSales,
  883. shopNameText: shopNameText,
  884. shopUrl: shopUrl,
  885. mainPic: mainPic
  886. }
  887. outObj.urls.push(info);
  888. }
  889. let nextHandle = document.querySelectorAll('[class*=quick-result] [class*=quick-num]');
  890. if(nextHandle && nextHandle.length > 0){
  891. nextHandle[1].click({force: true});
  892. }else{
  893. nextHandle = document.querySelector('[class*=pagination_center] [class*=pagination_next]');
  894. if(nextHandle && nextHandle.length > 0){
  895. nextHandle.click({force: true});
  896. }
  897. }
  898. return outObj;
  899. }, authority, this.execNum, itemTitle);
  900. this.jdList.push(imgInfo);
  901. });
  902. }
  903. await this.jdBrowser.close();
  904. this.addLoading = false;
  905. }catch(e){
  906. this.addLoading = false;
  907. this.showError(e);
  908. }
  909. })();
  910. },
  911. // 小红书扫描网址
  912. redScanUrl(url){
  913. (async () => {
  914. try{
  915. let authority = this.$refs.headerRef.authority.isAuthority;
  916. let page = await this.redBrowser.newPage();
  917. let responseVideo = [];
  918. let waitUntil = 'networkidle2';
  919. waitUntil = this.initDevelop().waitUntil;
  920. await page.goto(url, {waitUntil : waitUntil});
  921. let searchTitle = await page.title() || '小红书笔记';
  922. if(searchTitle){
  923. searchTitle = searchTitle.substring(0, 50);
  924. if(this.containsAnyChar(searchTitle)){ //判断是否含有特殊字符
  925. searchTitle = searchTitle.replace(/[\\/:*?"<>|#%&\s]/g, "");
  926. }
  927. }
  928. let pageInfo = await page.evaluate(() => {
  929. let cHeight = document.documentElement.clientHeight;
  930. let scrollHeight = document.body.scrollHeight;
  931. return {'scrollHeight': scrollHeight, 'cHeight': cHeight}
  932. });
  933. let scrollHeight = pageInfo.scrollHeight;
  934. let cHeight = pageInfo.cHeight;
  935. let num = Math.ceil(scrollHeight / cHeight);
  936. let start = -1;
  937. let scrollTime = this.initMs();
  938. let scrollInt = setInterval(async() => {
  939. if(this.redBrowser && !this.redBrowser.isConnected()){
  940. clearInterval(scrollInt);
  941. await this.redBrowser.close();
  942. this.redBrowser = null;
  943. }
  944. start ++;
  945. let outInfo = await page.evaluate((start, searchTitle, url) => {
  946. let scrollHeight = document.body.scrollHeight;
  947. let cHeight = document.documentElement.clientHeight;
  948. let outObj = {
  949. url: url,
  950. title: searchTitle,
  951. currentPage: 1,
  952. totalPage: 1,
  953. urls: [],
  954. status: '1',
  955. loading: false,
  956. scrollHeight: scrollHeight
  957. };
  958. let arr1 = document.querySelectorAll('[class=feeds-container]>section');
  959. for(let i=0; i< arr1.length; i++){
  960. let index = arr1[i].dataset.index || '';
  961. let href = arr1[i].querySelector('a[class*=cover]') ? arr1[i].querySelector('a[class*=cover]').href : '';
  962. let title = arr1[i].querySelector('[class*=title]') ? arr1[i].querySelector('[class*=title]').innerText : "";
  963. let realSales = arr1[i].querySelector('[class*=count]') ? arr1[i].querySelector('[class*=count]').innerText : 0;
  964. let mainPic = arr1[i].querySelector('img') ? arr1[i].querySelector('img').src : '';
  965. let name = arr1[i].querySelector('[class^=name]') ? arr1[i].querySelector('[class^=name]').innerText : "";
  966. let time = arr1[i].querySelector('[class^=time]') ? arr1[i].querySelector('[class^=time]').innerText : "";
  967. let info = {
  968. title: title,
  969. id: index,
  970. href: href,
  971. realSales: realSales,
  972. mainPic: mainPic,
  973. name: name,
  974. time: time
  975. }
  976. if(href || title || name){
  977. outObj.urls.push(info);
  978. }
  979. }
  980. window.scrollTo({
  981. top: cHeight * start,
  982. behavior: "smooth"
  983. });
  984. return outObj;
  985. }, start, searchTitle, url);
  986. if(this.redList.length == 0){
  987. this.redList.push(outInfo);
  988. }else{
  989. let flag = true;
  990. for(let i = 0; i < this.redList.length; i++){
  991. if(outInfo.url == this.redList[i].url){
  992. flag = false;
  993. let temp = this.redList[i].urls;
  994. temp = temp.concat(outInfo.urls);
  995. let list = temp.filter((item, index) => temp.findIndex(i => i.id === item.id) === index)
  996. this.redList[i].urls = list;
  997. }
  998. }
  999. if(flag){
  1000. this.redList.push(outInfo);
  1001. }
  1002. }
  1003. if(outInfo.scrollHeight > 0){
  1004. num = Math.ceil(outInfo.scrollHeight / cHeight);
  1005. }else{
  1006. num = 0;
  1007. }
  1008. if(start > num || start > 500){ // 防止页面过长,滚动200次自动停止
  1009. clearInterval(scrollInt);
  1010. await this.redBrowser.close();
  1011. this.redBrowser = null;
  1012. this.addLoading = false;
  1013. }
  1014. }, scrollTime);
  1015. }catch(e){
  1016. this.addLoading = false;
  1017. this.showError(e);
  1018. }
  1019. })();
  1020. },
  1021. // 普通扫描网址
  1022. commonScanUrl(url){
  1023. (async () => {
  1024. try{
  1025. let authority = this.$refs.headerRef.authority.isAuthority;
  1026. let page = await this.commonBrowser.newPage();
  1027. let responseVideo = [];
  1028. let waitUntil = 'networkidle2';
  1029. waitUntil = this.initDevelop().waitUntil;
  1030. await page.goto(url, {waitUntil : waitUntil});
  1031. let searchTitle = await page.title() || '网址链接';
  1032. if(searchTitle){
  1033. searchTitle = searchTitle.substring(0, 50);
  1034. if(this.containsAnyChar(searchTitle)){ //判断是否含有特殊字符
  1035. searchTitle = searchTitle.replace(/[\\/:*?"<>|#%&\s]/g, "");
  1036. }
  1037. }
  1038. let pageInfo = await page.evaluate(() => {
  1039. let cHeight = document.documentElement.clientHeight;
  1040. let scrollHeight = document.body.scrollHeight;
  1041. return {'scrollHeight': scrollHeight, 'cHeight': cHeight}
  1042. });
  1043. let scrollHeight = pageInfo.scrollHeight;
  1044. let cHeight = pageInfo.cHeight;
  1045. let num = Math.ceil(scrollHeight / cHeight);
  1046. let start = -1;
  1047. let scrollTime = this.initMs();
  1048. let scrollInt = setInterval(async() => {
  1049. if(this.commonBrowser && !this.commonBrowser.isConnected()){
  1050. clearInterval(scrollInt);
  1051. await this.commonBrowser.close();
  1052. this.commonBrowser = null;
  1053. }
  1054. start ++;
  1055. let scrollHeight2 = await page.evaluate((start) => {
  1056. let scrollHeight = document.body.scrollHeight;
  1057. let cHeight = document.documentElement.clientHeight;
  1058. window.scrollTo({
  1059. top: cHeight * start,
  1060. behavior: "smooth"
  1061. });
  1062. return scrollHeight;
  1063. }, start);
  1064. if(scrollHeight2 > 0){
  1065. num = Math.ceil(scrollHeight2 / cHeight);
  1066. }else{
  1067. num = 0;
  1068. }
  1069. if(start > num || start > 200){ // 防止页面过长,滚动200次自动停止
  1070. clearInterval(scrollInt);
  1071. let outInfo2 = await page.evaluate((searchTitle, url) => {
  1072. let outObj = {
  1073. url: url,
  1074. title: searchTitle,
  1075. currentPage: 1,
  1076. totalPage: 1,
  1077. urls: [],
  1078. status: '1',
  1079. loading: false
  1080. };
  1081. let arr1 = document.querySelectorAll('a[href^=http]');
  1082. let arr2 = document.querySelectorAll('a[href^="/"]');
  1083. for(let i=0; i< arr1.length; i++){
  1084. let href = arr1[i].href;
  1085. let title = arr1[i].innerText;
  1086. let info = {
  1087. title: title,
  1088. href: href
  1089. }
  1090. outObj.urls.push(info);
  1091. }
  1092. for(let j=0; j< arr2.length; j++){
  1093. let href = arr2[j].href;
  1094. let title = arr2[j].innerText;
  1095. let info = {
  1096. title: title,
  1097. href: href
  1098. }
  1099. outObj.urls.push(info);
  1100. }
  1101. return outObj;
  1102. }, searchTitle, url);
  1103. this.commonList.push(outInfo2);
  1104. await this.commonBrowser.close();
  1105. this.addLoading = false;
  1106. }
  1107. }, scrollTime);
  1108. }catch(e){
  1109. this.addLoading = false;
  1110. this.showError(e);
  1111. }
  1112. })();
  1113. },
  1114. // 检查天猫淘宝登录状态
  1115. checkLogin(){
  1116. this.checkLoading = true;
  1117. this.tbStatus = 1;
  1118. return new Promise((resolve, reject) => {
  1119. (async () => {
  1120. try{
  1121. if(this.loginBrowser){
  1122. await this.loginBrowser.close();
  1123. this.loginBrowser = null;
  1124. }
  1125. this.loginBrowser = await puppeteer.launch({
  1126. executablePath: this.initPath(),
  1127. args: ['--window-size=1280,800'],
  1128. userDataDir: this.initDataDir()
  1129. });
  1130. const page = await this.loginBrowser.newPage();
  1131. await page.setViewport({ width: 1280, height: 800 });
  1132. let testUrl = 'https://www.taobao.com';
  1133. await page.goto(testUrl, {waitUntil : 'networkidle2'});
  1134. let loginInfo = await page.evaluate(() => {
  1135. let navTags = document.querySelector('.site-nav-sign a');
  1136. let userTags = document.querySelector('.site-nav-user a');
  1137. if(navTags && navTags.innerHTML.indexOf('登录') > -1){
  1138. return false;
  1139. }else if(userTags){
  1140. return true;
  1141. }else{
  1142. return false;
  1143. }
  1144. });
  1145. if(loginInfo){
  1146. this.tbStatus = 2;
  1147. }else{
  1148. this.tbStatus = 3;
  1149. }
  1150. resolve(this.tbStatus);
  1151. this.checkLoading = false;
  1152. await this.loginBrowser.close();
  1153. this.loginBrowser = null;
  1154. }catch(e){
  1155. this.checkLoading = false;
  1156. reject(3);
  1157. this.showError(e);
  1158. }
  1159. })();
  1160. });
  1161. },
  1162. // 检查阿里巴巴登录状态
  1163. checkAlibabaLogin(){
  1164. this.checkLoading = true;
  1165. this.alibabaStatus = 1;
  1166. return new Promise((resolve, reject) => {
  1167. (async () => {
  1168. try{
  1169. if(this.loginBrowser){
  1170. await this.loginBrowser.close();
  1171. this.loginBrowser = null;
  1172. }
  1173. this.loginBrowser = await puppeteer.launch({
  1174. executablePath: this.initPath(),
  1175. args: ['--window-size=1280,800'],
  1176. userDataDir: this.initDataDir()
  1177. });
  1178. const page = await this.loginBrowser.newPage();
  1179. await page.setViewport({ width: 1280, height: 800 });
  1180. let testUrl = 'https://www.1688.com';
  1181. await page.goto(testUrl, {waitUntil : 'networkidle2'});
  1182. const cookies = await page.cookies();
  1183. let loginDiv = await page.$('div[class^=userNotLogin-]');
  1184. const loginEle = await page.$('div[class^=loginAvatar-]');
  1185. let loginText = '';
  1186. if (loginEle) {
  1187. loginText = await page.evaluate(el => el.innerText, loginEle);
  1188. }
  1189. const loginCookie = cookies.find(cookie =>
  1190. cookie.name === '__cn_logon__'
  1191. );
  1192. let logonValue = '';
  1193. if(loginCookie){
  1194. logonValue = loginCookie.value;
  1195. }
  1196. if(loginDiv || loginText == '登录' || logonValue == 'false' || logonValue == false){
  1197. this.alibabaStatus = 3; //未登录
  1198. }else{
  1199. this.alibabaStatus = 2;
  1200. }
  1201. resolve(this.alibabaStatus);
  1202. this.checkLoading = false;
  1203. await this.loginBrowser.close();
  1204. this.loginBrowser = null;
  1205. }catch(e){
  1206. this.checkLoading = false;
  1207. reject(3);
  1208. this.showError(e);
  1209. }
  1210. })();
  1211. });
  1212. },
  1213. // 检查京东登录状态
  1214. checkJdLogin(){
  1215. this.checkLoading = true;
  1216. this.jdStatus = 1;
  1217. return new Promise((resolve, reject) => {
  1218. (async () => {
  1219. try{
  1220. if(this.loginBrowser){
  1221. await this.loginBrowser.close();
  1222. this.loginBrowser = null;
  1223. }
  1224. this.loginBrowser = await puppeteer.launch({
  1225. executablePath: this.initPath(),
  1226. userDataDir: this.initDataDir('jd'),
  1227. args: [
  1228. '--start-maximized',
  1229. '--no-sandbox',
  1230. '--disable-setuid-sandbox',
  1231. '--disable-blink-features=AutomationControlled',
  1232. '--window-size=1280,800'
  1233. ]
  1234. });
  1235. const page = await this.loginBrowser.newPage();
  1236. await page.setViewport({ width: 1280, height: 800 });
  1237. let testUrl = 'https://www.jd.com';
  1238. await page.goto(testUrl, {waitUntil : 'networkidle2'});
  1239. let loginInfo = await page.evaluate(() => {
  1240. let navTags = document.querySelector('.link-login');
  1241. let userTags = document.querySelector('.nickname');
  1242. let userTags2 = document.querySelector('.nick'); //企业账号
  1243. let userInfo2 = document.querySelector('.login_info'); //页面右侧的登录信息
  1244. let userTags3 = false;
  1245. if(userInfo2 && userInfo2.innerText.indexOf('退出') > -1){
  1246. userTags3 = true;
  1247. }
  1248. if(navTags && navTags.innerHTML.indexOf('请登录') > -1 && !userTags3){
  1249. return false;
  1250. }else if(userTags || userTags2 || userTags3){
  1251. return true;
  1252. }else{
  1253. return false;
  1254. }
  1255. });
  1256. if(loginInfo){
  1257. this.jdStatus = 2;
  1258. }else{
  1259. this.jdStatus = 3;
  1260. }
  1261. resolve(this.jdStatus);
  1262. this.checkLoading = false;
  1263. await this.loginBrowser.close();
  1264. this.loginBrowser = null;
  1265. }catch(e){
  1266. this.checkLoading = false;
  1267. reject(3);
  1268. this.showError(e);
  1269. }
  1270. })();
  1271. });
  1272. },
  1273. // 检查小红书登录状态
  1274. checkRedLogin(){
  1275. this.checkLoading = true;
  1276. this.redStatus = 1;
  1277. return new Promise((resolve, reject) => {
  1278. (async () => {
  1279. try{
  1280. if(this.loginBrowser){
  1281. await this.loginBrowser.close();
  1282. this.loginBrowser = null;
  1283. }
  1284. puppeteer.use(StealthPlugin());
  1285. this.loginBrowser = await puppeteer.launch({
  1286. executablePath: this.initPath(),
  1287. userDataDir: this.initDataDir(),
  1288. args: [
  1289. '--start-maximized',
  1290. '--no-sandbox',
  1291. '--disable-setuid-sandbox',
  1292. '--disable-blink-features=AutomationControlled',
  1293. '--window-size=1280,800'
  1294. ],
  1295. });
  1296. const page = await this.loginBrowser.newPage();
  1297. await page.setViewport({ width: 1280, height: 800 });
  1298. let testUrl = "https://www.xiaohongshu.com";
  1299. await page.goto(testUrl, {waitUntil : 'networkidle2'});
  1300. let loginBtn = await page.$$('#login-btn');
  1301. let loginContainer = await page.$$('.login-container');
  1302. if(loginContainer.length > 0 || loginBtn.length > 0){
  1303. this.redStatus = 3; //未登录
  1304. }else{
  1305. this.redStatus = 2;
  1306. }
  1307. resolve(this.redStatus);
  1308. this.checkLoading = false;
  1309. await this.loginBrowser.close();
  1310. this.loginBrowser = null;
  1311. }catch(e){
  1312. this.checkLoading = false;
  1313. reject(3);
  1314. this.showError(e);
  1315. }
  1316. })();
  1317. });
  1318. },
  1319. // 去登录
  1320. loginUrl(url){
  1321. (async () => {
  1322. if(this.loginBrowser){
  1323. await this.loginBrowser.close();
  1324. this.loginBrowser = null;
  1325. }
  1326. try{
  1327. if (!fs.existsSync(os.tmpdir() + separator + 'chrome-data-capture')) {
  1328. fs.mkdirSync(os.tmpdir() + separator + 'chrome-data-capture');
  1329. }
  1330. if (!fs.existsSync(os.tmpdir() + separator + 'chrome-data-capture-jd')) {
  1331. fs.mkdirSync(os.tmpdir() + separator + 'chrome-data-capture-jd');
  1332. }
  1333. if (!fs.existsSync(os.tmpdir() + separator + 'chrome-data-capture-local')) {
  1334. fs.mkdirSync(os.tmpdir() + separator + 'chrome-data-capture-local');
  1335. }
  1336. let userDataDir = this.initDataDir();
  1337. if(url.indexOf('.jd.com/') > -1){
  1338. userDataDir = this.initDataDir('jd');
  1339. }
  1340. puppeteer.use(StealthPlugin());
  1341. this.loginBrowser = await puppeteer.launch({
  1342. headless: false,
  1343. executablePath: this.initPath(),
  1344. args: ['--window-size=1280,800'],
  1345. userDataDir: userDataDir,
  1346. });
  1347. const page = await this.loginBrowser.newPage();
  1348. await page.setViewport({ width: 1280, height: 800 });
  1349. await page.evaluateOnNewDocument(() => {
  1350. const newProto = navigator.__proto__;
  1351. delete newProto.webdriver;
  1352. navigator.__proto__ = newProto;
  1353. window.navigator.chrome = {
  1354. runtime: {}
  1355. };
  1356. });
  1357. await page.goto(url, {waitUntil : 'networkidle2'});
  1358. }catch(e){
  1359. this.showError(e);
  1360. }
  1361. })();
  1362. },
  1363. // 开始解析
  1364. async startParsing(){
  1365. if(this.formatUrl.trim()){
  1366. let formatUrl = this.formatUrl.trim();
  1367. if(formatUrl.indexOf('http://') > -1){
  1368. formatUrl = formatUrl.replace('http://', 'https://');
  1369. }
  1370. if(formatUrl.indexOf('https://') == -1){
  1371. this.$message.error('错了哦,请输入正确的视频地址(https://开头)');
  1372. return false;
  1373. }
  1374. let arr = formatUrl.split('https://');
  1375. formatUrl = 'https://' + arr[1];
  1376. this.downloadUrl = formatUrl;
  1377. const regex = /https:\/\/.*?.douyin.com/;
  1378. const res = regex.exec(formatUrl);
  1379. this.bUrl = true;
  1380. if(res && res.length > 0){ //抖音视频解析,使用puputter
  1381. this.selectIndex = -1;
  1382. this.videoList = [];
  1383. let reg2 = /[?&]modal_id=(\w+)/;
  1384. let res2 = reg2.exec(formatUrl);
  1385. if(res2){
  1386. let modal_id = res2[1];
  1387. formatUrl = res[0] + '/video/'+modal_id;
  1388. this.downloadUrl = formatUrl;
  1389. }
  1390. this.douyinParsing(formatUrl);
  1391. return false;
  1392. }
  1393. this.parseLoading = true;
  1394. this.tabLoading = true;
  1395. this.selectIndex = -1;
  1396. this.videoList = [];
  1397. let userAgent = [];
  1398. let setingAgent = '';
  1399. if(formatUrl.indexOf('weibo.com/') > -1 && setingAgent){
  1400. userAgent = [
  1401. '--add-header',
  1402. 'User-Agent:'+setingAgent,
  1403. '--add-header',
  1404. "Referer:https://weibo.com/"
  1405. ];
  1406. }
  1407. let params = [
  1408. '--no-playlist',
  1409. '--dump-json',
  1410. ...userAgent,
  1411. formatUrl
  1412. ];
  1413. electronApi.spawnExec(['dlp.exe', ...params]).then(res => {
  1414. this.parseLoading = false;
  1415. this.tabLoading = false;
  1416. let info = res.stdout ? res.stdout.toString() : '{formats: []}';
  1417. this.videoInfo = JSON.parse(info);
  1418. this.videoList = this.videoInfo.formats || [];
  1419. const bregex = /https:\/\/.*?.bilibili.com/;
  1420. const bres = bregex.exec(formatUrl);
  1421. if(bres && bres.length > 0){ //b站的视频
  1422. this.bUrl = false;
  1423. }else{
  1424. this.bUrl = true;
  1425. }
  1426. this.biliAudioList = [];
  1427. this.biliAudioIndex = -1;
  1428. this.biliVideoList = [];
  1429. this.biliVideoIndex = -1;
  1430. this.videoList.map(item => {
  1431. item.title = this.videoInfo.title,
  1432. item.status = '1';
  1433. if(!this.bUrl){ // b站的视频
  1434. if(item.resolution.indexOf('audio') > -1 || item.audio_ext != 'none'){
  1435. this.biliAudioList.push(item);
  1436. }else{
  1437. this.biliVideoList.push(item);
  1438. }
  1439. }
  1440. })
  1441. }).catch(err =>{
  1442. this.parseLoading = false;
  1443. this.tabLoading = false;
  1444. // console.log('err1',err.stderr.toString());
  1445. let errStr = err.stderr.toString();
  1446. this.bUrl = true;
  1447. if(errStr.indexOf('Unsupported URL') > -1){
  1448. this.videoParsing(formatUrl);
  1449. return false;
  1450. }else if(formatUrl.indexOf('xiaohongshu.com/') > -1 && errStr.indexOf('No video formats') > -1){
  1451. this.$notify.error({
  1452. title: '设置中登录小红书账号下载!',
  1453. message: errStr
  1454. });
  1455. this.videoParsing(formatUrl);
  1456. return false;
  1457. }else{
  1458. this.$notify.error({
  1459. title: '网址解析失败!',
  1460. message: errStr
  1461. });
  1462. }
  1463. })
  1464. }
  1465. },
  1466. // 复制链接地址
  1467. copyText(text){
  1468. navigator.clipboard.writeText(text).then(() => {
  1469. this.$message({message: '视频下载链接已成功复制到剪贴板', type: 'success'});
  1470. }).catch(err => {
  1471. this.$message.error('无法复制文本', err.toString());
  1472. });
  1473. },
  1474. // 点击复制
  1475. copy(index){
  1476. this.commonUrl = '';
  1477. this.thunderUrl = [];
  1478. let tag = this.videoList[index].tag;
  1479. let urlList = this.videoList[index].urlList;
  1480. let title = this.videoList[index].title;
  1481. let text = urlList[0];
  1482. if(urlList.length <= 0){
  1483. this.$message.error('无法复制下载地址');
  1484. return false;
  1485. }
  1486. if(tag == 'douyin'){
  1487. this.dyModal = true;
  1488. this.dyIndex = index;
  1489. urlList.map(uitem => {
  1490. if(uitem.startsWith('https://www.douyin.com/')){
  1491. this.commonUrl = uitem;
  1492. }
  1493. if(!title){
  1494. title = '抖音视频';
  1495. }
  1496. // console.log(`AA`+uitem +`&filename=`+title+`.mp4ZZ`);
  1497. let urlBuffer = Buffer.from(`AA`+uitem +`&filename=`+title+`.mp4ZZ`).toString('base64');
  1498. this.thunderUrl.push('thunder://'+urlBuffer);
  1499. })
  1500. }else{
  1501. this.copyText(text);
  1502. }
  1503. },
  1504. // 解析抖音视频
  1505. async douyinParsing(url){
  1506. if(this.loginBrowser){
  1507. await this.loginBrowser.close();
  1508. this.loginBrowser = null;
  1509. }
  1510. if(this.videoBrowser){
  1511. await this.videoBrowser.close();
  1512. this.videoBrowser = null;
  1513. }
  1514. if (!fs.existsSync(os.tmpdir() + separator + 'chrome-data-capture-video')) {
  1515. fs.mkdirSync(os.tmpdir() + separator + 'chrome-data-capture-video');
  1516. }
  1517. this.parseLoading = true;
  1518. this.tabLoading = true;
  1519. setTimeout(()=> {
  1520. this.parseLoading = false;
  1521. this.tabLoading = false;
  1522. }, 20000)
  1523. let userDataDir = os.tmpdir() + separator + 'chrome-data-capture-video';
  1524. // 运行不同平台的浏览器
  1525. puppeteer.use(StealthPlugin());
  1526. let headless = true;
  1527. headless = this.initDevelop().headless;
  1528. this.videoBrowser = await puppeteer.launch({
  1529. headless: headless,
  1530. executablePath: this.initPath(),
  1531. userDataDir: userDataDir,
  1532. args: [
  1533. '--start-maximized',
  1534. '--no-sandbox',
  1535. '--disable-setuid-sandbox',
  1536. '--disable-blink-features=AutomationControlled',
  1537. ]
  1538. });
  1539. await new Promise((resolve,reject) =>{
  1540. (async () => {
  1541. try{
  1542. let authority = this.$refs.headerRef.authority.isAuthority;
  1543. const page = await this.videoBrowser.newPage();
  1544. let responseVideo = [];
  1545. let responseUrl = [];
  1546. let responseObj = {};
  1547. let vtitle = '视频';
  1548. page.on('response', async(response) => {
  1549. // 检查响应的 MIME 类型是否以 'video/' 开头
  1550. if (response.headers()['content-type'] && response.headers()['content-type'].startsWith('video/')) {
  1551. if(responseVideo.indexOf(response.url()) < 0 && !response.url().startsWith('blob:https://')){
  1552. responseVideo.push(response.url());
  1553. vtitle = await page.title();
  1554. if(vtitle){
  1555. vtitle = vtitle.substring(0, 50);
  1556. if(this.containsAnyChar(vtitle)){ //判断是否含有特殊字符
  1557. vtitle = vtitle.replace(/[\\/:*?"<>|#%&\s]/g, "");
  1558. }
  1559. }
  1560. }
  1561. }
  1562. if (response.headers()['content-type'] && response.headers()['content-type'].startsWith('application/json')) {
  1563. if(response.url().indexOf('/aweme/detail/') > -1){
  1564. if(responseUrl.indexOf(response.url()) < 0){
  1565. responseUrl.push(response.url());
  1566. }else{
  1567. return false;
  1568. }
  1569. let jsonText = await response.text();
  1570. if(jsonText && typeof jsonText == 'string'){
  1571. responseObj = JSON.parse(jsonText);
  1572. }
  1573. }
  1574. }
  1575. });
  1576. let waitUntil = 'networkidle2';
  1577. waitUntil = this.initDevelop().waitUntil;
  1578. await page.goto(url, {waitUntil : waitUntil});
  1579. await page.waitForTimeout(1000);
  1580. await this.videoBrowser.close();
  1581. if(responseObj['aweme_detail']){ // 返回的接口数据中有aweme_detail参数 才会解析接口数据
  1582. let arr = ['aweme_detail']; //'video' ,'play_addr', 'url_list', '2'];
  1583. for(let i = 0; i < arr.length; i++){
  1584. responseObj = responseObj[arr[i]];
  1585. }
  1586. if(responseObj && responseObj.preview_title){
  1587. responseObj.preview_title = responseObj.preview_title.substring(0, 50);
  1588. if(this.containsAnyChar(responseObj.preview_title)){ //判断是否含有特殊字符
  1589. responseObj.preview_title = responseObj.preview_title.replace(/[\\/:*?"<>|#%&\s]/g, "");
  1590. }
  1591. }
  1592. if(responseObj && responseObj.video && responseObj.video.play_addr){
  1593. let vinfo = {
  1594. title: responseObj.preview_title,
  1595. tag: 'douyin',
  1596. format_id: 'default',
  1597. ext: 'mp4',
  1598. resolution: responseObj.video.play_addr.width + 'x' + responseObj.video.play_addr.height,
  1599. fps: '-',
  1600. filesize: responseObj.video.play_addr.data_size,
  1601. vcodec: '-',
  1602. acodec: '-',
  1603. urlList: responseObj.video.play_addr.url_list,
  1604. status: '1',
  1605. loading: false
  1606. }
  1607. this.videoList.push(vinfo);
  1608. }
  1609. if(responseObj && responseObj.video && responseObj.video.play_addr_265){
  1610. let vinfo = {
  1611. title: responseObj.preview_title,
  1612. tag: 'douyin',
  1613. format_id: 'play_addr_265',
  1614. ext: 'mp4',
  1615. resolution: responseObj.video.play_addr_265.width + 'x' + responseObj.video.play_addr_265.height,
  1616. fps: '-',
  1617. filesize: responseObj.video.play_addr_265.data_size,
  1618. vcodec: '-',
  1619. acodec: '-',
  1620. urlList: responseObj.video.play_addr_265.url_list,
  1621. status: '1',
  1622. loading: false
  1623. }
  1624. this.videoList.push(vinfo);
  1625. }
  1626. if(responseObj && responseObj.video && responseObj.video.play_addr_h264){
  1627. let vinfo = {
  1628. title: responseObj.preview_title,
  1629. tag: 'douyin',
  1630. format_id: 'play_addr_h264',
  1631. ext: 'mp4',
  1632. resolution: responseObj.video.play_addr_h264.width + 'x' + responseObj.video.play_addr_h264.height,
  1633. fps: '-',
  1634. filesize: responseObj.video.play_addr_h264.data_size,
  1635. vcodec: '-',
  1636. acodec: '-',
  1637. urlList: responseObj.video.play_addr_h264.url_list,
  1638. status: '1',
  1639. loading: false
  1640. }
  1641. this.videoList.push(vinfo);
  1642. }
  1643. }else if(responseVideo.length > 0){ //网页解析到视频地址
  1644. let vinfo = {
  1645. title: vtitle,
  1646. tag: 'douyin',
  1647. format_id: 'video',
  1648. ext: 'mp4',
  1649. resolution: '-',
  1650. fps: '-',
  1651. filesize: '',
  1652. vcodec: '-',
  1653. acodec: '-',
  1654. urlList: responseVideo,
  1655. status: '1',
  1656. loading: false
  1657. }
  1658. this.videoList.push(vinfo);
  1659. }
  1660. this.parseLoading = false;
  1661. this.tabLoading = false;
  1662. }catch(e){
  1663. this.parseLoading = false;
  1664. this.tabLoading = false;
  1665. reject(e);
  1666. this.showError(e);
  1667. }
  1668. })();
  1669. });
  1670. },
  1671. // 下载网址链接的图片
  1672. async downloadImage(imageUrl, outputPath, urlInfo) {
  1673. let _this = this;
  1674. let received_bytes = 0;
  1675. let total_bytes = 0;
  1676. try {
  1677. let req = request({
  1678. method: 'GET', uri: imageUrl, strictSSL: false
  1679. });
  1680. let out = fs.createWriteStream(outputPath);
  1681. req.pipe(out);
  1682. return new Promise((resolve, reject) => {
  1683. req.on('response', (data) => {
  1684. total_bytes = parseInt(data.headers['content-length']);
  1685. const status = data.statusCode;
  1686. if(this.menuIndex != '1'){
  1687. if (status < 200 || status >= 300) {
  1688. this.$notify.error({
  1689. title: '网络资源访问异常!- 1',
  1690. message: imageUrl.slice(0,50)
  1691. });
  1692. }else if(isNaN(total_bytes)){
  1693. this.$notify.error({
  1694. title: '网络资源访问异常!- 2',
  1695. message: imageUrl.slice(0,50)
  1696. });
  1697. }else{
  1698. // console.log('下载中...')
  1699. }
  1700. }
  1701. if(status == 403 || isNaN(total_bytes)){
  1702. fs.unlinkSync(outputPath);
  1703. urlInfo.status = '4';
  1704. }
  1705. });
  1706. req.on('data', (chunk) => {
  1707. received_bytes += chunk.length;
  1708. if(urlInfo.filesize){
  1709. let size = Number(urlInfo.filesize);
  1710. let percent = Number(received_bytes / size * 100);
  1711. urlInfo.percent = percent.toFixed(2);
  1712. if(percent > 100){
  1713. urlInfo.percent = 100;
  1714. }
  1715. this.$forceUpdate();
  1716. }
  1717. });
  1718. req.on('end', ()=> {
  1719. urlInfo.status = '3';
  1720. //console.log('下载完成', outputPath)
  1721. resolve(true);
  1722. });
  1723. });
  1724. } catch (error) {
  1725. urlInfo.status = '4';
  1726. console.error(imageUrl, `Failed to download image: ${error.message}`);
  1727. throw error;
  1728. }
  1729. },
  1730. // 获取页面标题 - 生成对应的文件夹
  1731. async getTitle(page, urlInfo){
  1732. // 已页面标题作为新建文件夹,保留前50个字
  1733. let title = await page.title();
  1734. if(title){
  1735. title = title.substring(0, 50);
  1736. if(this.containsAnyChar(title)){ //判断是否含有特殊字符
  1737. title = title.replace(/[\\/:*?"<>|#%&\s]/g, "");
  1738. }
  1739. if (fs.existsSync(this.downloadDir + separator + pjson.softInfo.softName + separator + title)) {
  1740. urlInfo.newPath = this.downloadDir + separator + pjson.softInfo.softName + separator + title;
  1741. } else {
  1742. fs.mkdirSync(this.downloadDir + separator + pjson.softInfo.softName + separator + title);
  1743. urlInfo.newPath = this.downloadDir + separator + pjson.softInfo.softName + separator + title;
  1744. }
  1745. }
  1746. },
  1747. // 下载base64位的图片
  1748. async downloadBaseImage(base64String, outputPath, urlInfo) {
  1749. const buffer = Buffer.from(base64String, 'base64');
  1750. const writeStream = fs.createWriteStream(outputPath);
  1751. writeStream.write(buffer);
  1752. writeStream.end();
  1753. writeStream.on('finish', () => {
  1754. urlInfo.num += 1;
  1755. });
  1756. // 监听错误事件
  1757. writeStream.on('error', (err) => {
  1758. console.error('base64位写入文件时出错:', err);
  1759. });
  1760. },
  1761. // 错误提示
  1762. showError(e){
  1763. let str = '';
  1764. if(e.toString().indexOf('ERR_NAME_NOT_RESOLVE') > -1){
  1765. str = '无法解析该网址,请查看网址是否正确!-1';
  1766. }else if(e.toString().indexOf('Cannot navigate to invalid URL') > -1){
  1767. str = '无效的网址,请查看网址格式是否正确!-2';
  1768. }else if(e.toString().indexOf('ERR_CONNECTION_TIMED_OUT') > -1){
  1769. str = '链接请求超时,请查看网络状态!-3';
  1770. }else if(e.toString().indexOf('TimeoutError') > -1){
  1771. str = '链接请求超时,请查看网络状态!-4';
  1772. }else if(e.toString().indexOf('operation not permitted') > -1){
  1773. str = '权限受限,请以管理员权限运行软件!-5';
  1774. }else if(e.toString().indexOf('browser has disconnected') > -1){
  1775. str = '内置浏览器已关闭!-6';
  1776. }else if(e.toString().indexOf('Failed to launch the browser') > -1){
  1777. str = '请先关闭内置浏览器!-7';
  1778. }else{
  1779. str = e.toString();
  1780. //console.log(e);
  1781. }
  1782. this.loading = false;
  1783. this.$notify.error({
  1784. title: '提示',
  1785. message: str
  1786. });
  1787. },
  1788. // 是否包含特殊字符
  1789. containsAnyChar(str) {
  1790. let charsArray = ['\\', '/', ':', '*', '?', '"', '<', '>', '|', '#', '%' ,'&', ' ', '\t', '\n'];
  1791. for (let i = 0; i < charsArray.length; i++) {
  1792. if (str.includes(charsArray[i])) {
  1793. return true;
  1794. }
  1795. }
  1796. return false;
  1797. },
  1798. // 随机生成字符
  1799. randomString(length) {
  1800. let result = '';
  1801. const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
  1802. const charactersLength = characters.length;
  1803. for (let i = 0; i < length; i++ ) {
  1804. result += characters.charAt(Math.floor(Math.random() * charactersLength));
  1805. }
  1806. return result;
  1807. },
  1808. //
  1809. formatSeconds(value) {
  1810. var secondTime = parseInt(value); // 秒
  1811. var minuteTime = 0; // 分
  1812. var hourTime = 0; // 小时
  1813. if (secondTime >= 60) {
  1814. minuteTime = parseInt(secondTime / 60);
  1815. secondTime = parseInt(secondTime % 60);
  1816. if (minuteTime >= 60) {
  1817. hourTime = parseInt(minuteTime / 60);
  1818. minuteTime = parseInt(minuteTime % 60);
  1819. }
  1820. }
  1821. var result ="" +(parseInt(secondTime) < 10? "0" + parseInt(secondTime): parseInt(secondTime));
  1822. result ="" + (parseInt(minuteTime) < 10? "0" + parseInt(minuteTime) : parseInt(minuteTime)) + ":" + result;
  1823. result ="" + (parseInt(hourTime) < 10 ? "0" + parseInt(hourTime): parseInt(hourTime)) +":" + result;
  1824. return result;
  1825. },
  1826. // 视频时间转为秒
  1827. getSs(rawDuration){
  1828. const $ar = rawDuration.split(":");
  1829. let $duration = parseFloat($ar[2]);
  1830. if ($ar[1]) $duration += parseInt($ar[1]) * 60;
  1831. if ($ar[0]) $duration += parseInt($ar[0]) * 60 * 60;
  1832. return $duration;
  1833. },
  1834. }
  1835. };
  1836. </script>
  1837. <style lang="scss">
  1838. @import "../assets/css/fontx/iconfont.css";
  1839. @import "../assets/css/home.scss";
  1840. .ivu-input-number-controls-outside-btn i {
  1841. font-weight: 800;
  1842. }
  1843. .update-point {
  1844. display: inline-block;
  1845. width: 8px;
  1846. height: 8px;
  1847. border-radius: 8px;
  1848. background: #ff0000;
  1849. top: 14px;
  1850. position: absolute;
  1851. left: -13px;
  1852. }
  1853. .menu-item {
  1854. padding: 8px 0;
  1855. font-size: 14px;
  1856. .iconfont {
  1857. font-size: 32px;
  1858. }
  1859. &:hover,
  1860. &.active {
  1861. color: #ed4014;
  1862. }
  1863. }
  1864. .ivu-progress-show-info .ivu-progress-outer {
  1865. padding-right: 40px !important;
  1866. margin-right: -40px !important;
  1867. }
  1868. .tips {
  1869. text-align: center;
  1870. padding: 10px 0;
  1871. color: #ed4014;
  1872. font-size: 12px;
  1873. }
  1874. .handle-desc {
  1875. display: inline-block;
  1876. width: calc(100% - 100px);
  1877. overflow: hidden;
  1878. }
  1879. .ivu-menu-submenu-title {
  1880. font-weight: 600;
  1881. }
  1882. .ivu-menu .ivu-menu-item {
  1883. line-height: 1;
  1884. }
  1885. // new-el
  1886. .el-menu {
  1887. border-right: none !important;
  1888. }
  1889. .cmenu-item {
  1890. padding: 0 20px 20px;
  1891. margin-bottom: 15px;
  1892. .cmenu-title {
  1893. font-size: 18px;
  1894. font-weight: 600;
  1895. padding-bottom: 20px;
  1896. }
  1897. .citem-nav {
  1898. text-align: center;
  1899. border-radius: 10px;
  1900. min-height: 110px;
  1901. padding: 15px 0;
  1902. background-color: #fff;
  1903. font-size: 15px;
  1904. cursor: pointer;
  1905. &.bg-linear1 {
  1906. color: #fff;
  1907. font-size: 20px;
  1908. background: linear-gradient(to right bottom, #2A56CA, #5795F4);
  1909. }
  1910. &.bg-linear2 {
  1911. color: #fff;
  1912. font-size: 20px;
  1913. background: linear-gradient(to right top, #147FBB, #5EB3E3);
  1914. }
  1915. &.bg-linear3 {
  1916. color: #fff;
  1917. font-size: 20px;
  1918. background: linear-gradient(to right bottom, #2F9E8A, #56CDB1);
  1919. }
  1920. &:hover {
  1921. margin-top: -5px;
  1922. box-shadow: 3px 3px 6px #ccc, -3px -3px 6px #ccc;
  1923. }
  1924. .citem-img {
  1925. width: 50px;
  1926. margin-bottom: 10px;
  1927. }
  1928. }
  1929. }
  1930. .popper-open{
  1931. text-align: center !important;
  1932. padding: 10px !important;
  1933. background: #303133 !important;
  1934. color: #fff !important;
  1935. min-width: 120px !important;
  1936. opacity: 0.8;
  1937. }
  1938. .popper-open[x-placement^=bottom] .popper__arrow::after{
  1939. border-bottom-color: #303133 !important;
  1940. }
  1941. textarea.el-textarea__inner{
  1942. height: 100% !important;
  1943. font-family: "Helvetica Neue",Helvetica,"PingFang SC","Hiragino Sans GB","Microsoft YaHei","微软雅黑",Arial,sans-serif;
  1944. }
  1945. .set-item{
  1946. margin-right: 15px;
  1947. .set-title{
  1948. font-size: 13px;
  1949. }
  1950. }
  1951. .outarea{
  1952. height: 100%;
  1953. border: 1px solid #DCDFE6;
  1954. background-color: #4851a415;
  1955. padding: 15px;
  1956. font-size: 20px;
  1957. overflow: hidden auto;
  1958. }
  1959. .outtext{
  1960. height: 100%;
  1961. font-size: 20px;
  1962. overflow: hidden auto;
  1963. textarea{
  1964. background-color: #4851a415;
  1965. }
  1966. }
  1967. .pin-tips{
  1968. font-size: 14px;
  1969. position: absolute;
  1970. bottom: 10px;
  1971. color: #ff0000;
  1972. left: 0;
  1973. right: 0;
  1974. margin: auto;
  1975. text-align: center;
  1976. }
  1977. .outarea.red-border{
  1978. border: 1px solid #F56C6C;
  1979. }
  1980. .outtext.red-border textarea{
  1981. border: 1px solid #F56C6C;
  1982. }
  1983. h3{
  1984. margin: 0;
  1985. }
  1986. .m-image{
  1987. width: 20px;
  1988. margin-right: 5px;
  1989. }
  1990. .dialog-footer-center{
  1991. text-align: center;
  1992. }
  1993. .visible-tips-style{
  1994. font-size: 18px;
  1995. color: #f73131;
  1996. font-weight: 600;
  1997. padding: 30px 40px 0;
  1998. }
  1999. .no-select{
  2000. user-select: none;
  2001. }
  2002. .tips-flex{
  2003. display: flex;
  2004. flex-wrap: nowrap;
  2005. justify-content: space-around;
  2006. align-items: center;
  2007. .el-icon-s-opportunity{
  2008. font-size: 50px;
  2009. color: #f73131;
  2010. margin-right: 10px;
  2011. }
  2012. .m-title{
  2013. flex: 1;
  2014. color: #f73131;
  2015. }
  2016. }
  2017. .c-titles{
  2018. background: -webkit-linear-gradient(left, #FF993F, #FFCE7C 25%, #FF9682 50%, #8e430d 75%, #FF993F);
  2019. color: transparent;
  2020. -webkit-background-clip: text;
  2021. background-size: 200% 100%;
  2022. animation: mask-ani 6s infinite linear;
  2023. font-size: 30px;
  2024. font-weight: 600;
  2025. line-height: 1.5;
  2026. }
  2027. @-webkit-keyframes mask-ani {
  2028. 0% {
  2029. background-position: 0 0;
  2030. }
  2031. 50% {
  2032. background-position: 100% 0;
  2033. }
  2034. 100% {
  2035. background-position: 200% 0;
  2036. }
  2037. }
  2038. .soft-icon{
  2039. margin: 0 3px;
  2040. }
  2041. .el-upload-list{
  2042. display: none;
  2043. }
  2044. </style>