前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Electron+Vue开发爬虫客户端2-自动下载网页文件

Electron+Vue开发爬虫客户端2-自动下载网页文件

作者头像
码客说
发布2021-01-12 14:39:02
3.2K0
发布2021-01-12 14:39:02
举报
文章被收录于专栏:码客

创建项目

尽量用图形化界面创建项目 安装插件也方便

代码语言:javascript
复制
vue ui

安装插件

vue-cli-plugin-electron-builder

插件官网地址: https://nklayman.github.io/vue-cli-plugin-electron-builder/

Choose Electron Version选择默认即可

运行报错

INFO Launching Electron… Failed to fetch extension, trying 4 more times Failed to fetch extension, trying 3 more times Failed to fetch extension, trying 2 more times Failed to fetch extension, trying 1 more times Failed to fetch extension, trying 0 more times Vue Devtools failed to install: Error: net::ERR_CONNECTION_TIMED_OUT

这是因为Devtools的安装需要翻墙

注释掉src/background.js中的以下代码就行了

代码语言:javascript
复制
if (isDevelopment && !process.env.IS_TEST) {
    // Install Vue Devtools
    try {
        await installVueDevtools();
    } catch (e) {
        console.error("Vue Devtools failed to install:", e.toString());
    }
}

加载页面

官方文档:https://www.electronjs.org/docs/api/webview-tag

页面添加webview

代码语言:javascript
复制
<webview
ref="mwv"
class="webview"
nodeintegration
disablewebsecurity
></webview>

配置中开启webview标签

代码语言:javascript
复制
const win = new BrowserWindow({
  width: 1200,
  height: 600,
  webPreferences: {
    webviewTag: true,
    webSecurity: false,
    enableRemoteModule: true,
    nodeIntegration: true
  }
});

获取页面Cookie

页面

代码语言:javascript
复制
<webview
  ref="mwv"
  class="webview"
  partition="persist:psvmc"
  nodeintegration
  disablewebsecurity
></webview>

JS

代码语言:javascript
复制
const { session } = window.require("electron").remote;

var ses = session.fromPartition("persist:psvmc");
ses.cookies
  .get({ url: "http://www.psvmc.cn" })
  .then(function(cookies) {
    console.log(cookies);
  });

也可以使用默认session

代码语言:javascript
复制
<webview
  ref="mwv"
  class="webview"
  nodeintegration
  disablewebsecurity
></webview>

js

代码语言:javascript
复制
const { session } = window.require("electron").remote;

var ses = session.defaultSession;
ses.cookies
  .get({ url: "http://www.psvmc.cn" })
  .then(function(cookies) {
    console.log(cookies);
  });

注意

webview和外层BrowserWindow内是可以共享session和cookie的。

Preload

加载要调用的JS

file:///Users/zhangjian/psvmc/app/me/web/91crawler2/public/mypreload.js

mypreload.js

文件放在了项目根目录的public文件夹下

代码语言:javascript
复制
if (require("electron").remote) {
  window.showData = function() {
    const a_arr = document.getElementsByTagName("a");
    console.info(a_arr);
    const href_arr = [];
    for (const a of a_arr) {
      const url = a.href;
      if (url.indexOf("beike_id") !== -1) {
        const idstr = url.split("beike_id/")[1].split("/")[0];
        href_arr.push({
          url: "http://www.psvmc.cn/Lesson/down/id/" + idstr,
          name: a.innerText
        });
      }
    }
    return href_arr;
  };
}

加载js和网页

代码语言:javascript
复制
openUrl: function() {
  const mwv = this.$refs["mwv"];
  mwv.src = this.weburl;
  mwv.preload =
    "file:///Users/zhangjian/psvmc/app/me/web/91crawler2/public/mypreload.js";
},

注意

  1. E lectron-Vue项目在运行时页面是以URL加载的,那么加载preload.js就必须用file://协议加载
  2. 目前还没有方法同时兼容开发和打包后获取preload.js的路径的方法,所有我暂时就先用dialog来选择文件路径了
  3. 一定要先设置preload再打开页面,当然同时设置也是可以的

调用其中的方法获取返回数据

代码语言:javascript
复制
myfun: function() {
  var that = this;
  const mwv = this.$refs["mwv"];
  mwv.executeJavaScript("showData();").then(function(data) {
    that.res_list = [];
    if (data) {
      that.res_list = that.res_list.concat(data);
    }
  });
}

弹窗选择文件

代码语言:javascript
复制
selectFile: function() {
  const that = this;
  dialog
    .showOpenDialog({
      properties: ["openFile", "openDirectory"]
    })
    .then(result => {
      if (!result.canceled) {
        that.outpath = result.filePaths[0];
      }
    })
    .catch(err => {
      console.log(err);
    });
},

下载文件

下载文件有两种方式

方式1 调用浏览器下载

代码语言:javascript
复制
downloadfileByUrl: function(murl) {
  session.defaultSession.downloadURL(murl);
},

监听下载进度方式

代码语言:javascript
复制
session.defaultSession.on("will-download", (event, item) => {
  const filePath = path.join(
    app.getPath("documents"),
    item.getFilename()
  );
  console.info("即将下载:", filePath);
  // item.setSavePath(filePath);
  item.on("updated", (event, state) => {
    if (state === "interrupted") {
      console.log("Download is interrupted but can be resumed");
    } else if (state === "progressing") {
      if (item) {
        const dp = (item.getReceivedBytes() * 100) / item.getTotalBytes();
        console.log(`${item.getFilename()}: ${dp}%`);
      }
    }
  });
  item.once("done", (event, state) => {
    if (state === "completed") {
      console.log("下载完成");
    } else {
      console.log(`下载失败: ${state}`);
    }
  });
});

官方说的设置下载位置后就不会弹出选择下载位置弹窗,但是实际并不生效(补充:在主进程中有效)

代码语言:javascript
复制
item.setSavePath(filePath);

优缺点

这种方式能保证下载文件名称中文不会乱码,但是官方给出的取消默认的下载行为再手动下载的方式行不通,后来发现是在渲染层的session的will-download中不能下载行为或者是取消弹窗,但是在主进程里是可以的。 也就是说渲染进程中可以获取下载进度但是没法设置下载位置, 所以在下载地址需要重定向获取的前提下可行的方案有

  • 在主线程中设置文件保存的位置,渲染进程中获取文件的下载进度。
  • 主线程获取真正的下载地址后调用event.preventDefault();取消默认的下载,手动用NodeJS下载。

主进程中的配置

代码语言:javascript
复制
const path = require("path");
win.webContents.session.on("will-download", (event, item) => {
  const filePath = path.join(app.getPath("downloads"), item.getFilename());
  item.setSavePath(filePath);

  item.on("updated", (event, state) => {
    if (state === "interrupted") {
      console.log("Download is interrupted but can be resumed");
    } else if (state === "progressing") {
      if (item.isPaused()) {
        console.log("Download is paused");
      } else {
        console.log(`Received bytes: ${item.getReceivedBytes()}`);
      }
    }
  });
  item.once("done", (event, state) => {
    if (state === "completed") {
      console.log("Download successfully");
    } else {
      console.log(`Download failed: ${state}`);
    }
  });
});

获取文件下载路径后取消下载,把下载地址发送到渲染进程中

代码语言:javascript
复制
win.webContents.session.on("will-download", (event, item) => {
  let fileURL = item.getURL();
  let fileName = item.getFilename();
  event.preventDefault();
});

那会不会是session对象不一致呢

代码语言:javascript
复制
const { remote } = window.require("electron");
let webContent = remote.getCurrentWebContents();
webContent.session.on("will-download", (event, item) => {
  const filePath = path.join(
    app.getPath("downloads"),
    item.getFilename()
  );
  item.setSavePath(filePath);
});

在渲染进程中获取webContent.session进行监听,回调中设置存储位置依旧会出现选择下载位置的弹窗,所以

event.preventDefault();item.setSavePath(filePath);只能在主进程中生效。

方式2 使用NodeJS下载

目前我使用的就是这种方式,推荐使用。 但是如果使用加载静态页面加载到window中的页面无法共享webview中的cookie

对于下载文件地址会重定向,所以使用了follow-redirects这个库。

代码语言:javascript
复制
downloadfileByUrl: function(murl) {
  const fs = window.require("fs");
  const iconv = require("iconv-lite");
  const url = require("url");
  const http = require("follow-redirects").http;
  const options = url.parse(murl);

  const request = http.request(options, response => {
    let filename_all = response.headers["content-disposition"];
    const file_length = response.headers["content-length"];
    let downd_length = 0;
    if (filename_all) {
      let buffer = iconv.encode(filename_all, "iso-8859-1");
      filename_all = iconv.decode(buffer, "utf8");
      console.info(filename_all);
      let filename = filename_all.split('"')[1];
      const filepath = app.getPath("downloads") + "/" + filename;
      console.info(filepath);
      if (fs.existsSync(filepath)) {
        fs.unlinkSync(filepath);
      }
      response.on("data", chunk => {
        downd_length += chunk.length;
        fs.writeFile(
          filepath,
          chunk,
          { flag: "a", encoding: "utf-8", mode: "0666" },
          function(err) {
            if (err) {
              console.log("文件写入失败");
            } else {
              console.info(
                "下载进度:" +
                  Math.ceil((downd_length * 100) / file_length) +
                  "%"
              );
            }
          }
        );
      });
      response.on("end", function() {
        console.info(filename + "下载完成");
      });
    }
  });
  request.end();
},

优缺点

这种方式能够完全自己管控下载的位置及流程

文件名乱码解决方式

NodeJS获取content-disposition中的文件名中文乱码的解决方法

代码语言:javascript
复制
const iconv = require("iconv-lite");      
let buffer = iconv.encode(filename_all, "iso-8859-1");
filename_all = iconv.decode(buffer, "utf8");

设置Cookie

如果Electron加载本地静态页面中请求是无法携带Cookie,就需要我们自己填上Cookie的头

代码语言:javascript
复制
getcookie: function () {
    let that = this;
    const ses = session.defaultSession;
    ses.cookies
        .get({url: "http://www.91taoke.com"})
        .then(function (cookies) {
            console.log(cookies);
            that.mcookie = cookies;
        });
},
downloadfileByUrl: function (murl) {
    const fs = window.require("fs");
    const iconv = require("iconv-lite");
    const url = require("url");
    const http = require("follow-redirects").http;
    const options = url.parse(murl);
    let mcookie = this.mcookie;
    let cookieStr = "";
    for (const mc of mcookie) {
        cookieStr += mc.name + "=" + mc.value + ";"
    }
    options.headers = {
        'Cookie': cookieStr,
        'Accept': '/ ',
        'Connection': 'keep-alive'
    }

    const request = http.request(options, response => {
        let filename_all = response.headers["content-disposition"];
        const file_length = response.headers["content-length"];
        let downd_length = 0;
        if (filename_all) {
            let buffer = iconv.encode(filename_all, "iso-8859-1");
            filename_all = iconv.decode(buffer, "utf8");
            console.info(filename_all);
            let filename = filename_all.split('"')[1];
            const filepath = app.getPath("downloads") + "/" + filename;
            console.info(filepath);
            if (fs.existsSync(filepath)) {
                fs.unlinkSync(filepath);
            }
            response.on("data", chunk => {
                downd_length += chunk.length;
                fs.writeFile(
                    filepath,
                    chunk,
                    {flag: "a", encoding: "utf-8", mode: "0666"},
                    function (err) {
                        if (err) {
                            console.log("文件写入失败");
                        } else {
                            console.info(
                                "下载进度:" +
                                Math.ceil((downd_length * 100) / file_length) +
                                "%"
                            );
                        }
                    }
                );
            });
            response.on("end", function () {
                console.info(filename + "下载完成");
            });
        }
    });
    request.end();
},
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2021-01-06,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 创建项目
  • 加载页面
  • 获取页面Cookie
  • Preload
  • 弹窗选择文件
  • 下载文件
    • 方式1 调用浏览器下载
      • 方式2 使用NodeJS下载
        • 文件名乱码解决方式
        • 设置Cookie
    领券
    问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档