使用pyinstaller对scrapy+selenium+pyqt5项目进行打包常见问题总结以及主要过程记录

前一段时间构建了一个使用scrapy+selenium+pyqt5的爬虫可视化界面,用于爬取知乎、百度百家号以及新浪新闻,在界面调试无误后,就需要使用pyinstaller进行打包,将项目变成更容易移植的exe文件。这篇博文主要用于记录打包的主要过程以及问题的解决方法。

目录

主要问题的解决

1. pyinstaller的安装

2. pyinstaller的运行

3. pyinstaller打包时出现编码错误问题

4. pyinstaller打包后只有黑框,未出现界面也并未闪退

5. 打包后黑框中出现警告scrapy15.0,scrapy.contrib.downloadermiddleware.useragent` is deprecated

6. 在GUI界面运行爬虫程序时出现AttributeError: Can't get attribute 'crawl' on

7.exe文件中反复运行程序打开多个GUI

8. 运行爬虫时selenium错误selenium.common.exceptions.WebDriverException: Message: 'chromedriver' executable needs to be in Path

打包过程的简单记录

1. 初始打包命令

2. 进阶打包命令——在命令中添加打包资源文件

3. 打包时对原有py文件的改动

4. 其他


主要问题的解决

1. pyinstaller的安装

一般来说,使用常用的pip命令即可安装,但我在安装时遇到了如下问题:

Installing build dependencies ... error
ERROR: Command errored out with exit status 1:

百度后得到解决方法,使用如下命令进行安装:

pip install --user pyinstaller

 

2. pyinstaller的运行

在安装之后,通过命令行运行pyinstaller时页出现了错误:

'pyinstaller' 不是内部或外部命令,也不是可运行的程序 或批处理文件。

这个是由于pyinstaller所在的路径并未添加到系统的环境变量之中,解决方法也很简单,在安装pyinstaller时,如果路径未添加到环境变量,会有相应提示,提示中包含此时pyinstaller的scripts路径。在win10系统中,我们可以直接在搜索栏搜索“查看高级系统设置”,然后点击“环境变量”选项,将pyinstaller的路径复制到path中并且点击“确定”保存,pip和pyinstaller等命令如果要正确运行,需在系统变量PATH中再加入"你的python安装地址\Scripts",例如"C:\Python37-32\Scripts",因为这些命令的exe文件被存放在Scripts文件夹里,重启命令行,即可正常运行pyinstaller命令。

 

3. pyinstaller打包时出现编码错误问题

Pyinstaller打包出现如下错误:

UnicodeDecodeError: 'utf-8' codec can't decode byte 0xce in position

 解决方法是先运行下面的命令:

chcp 65001

再运行原本的打包命令。同时需要注意打包文件目录中不包含中文。

 

4. pyinstaller打包后只有黑框,未出现界面也并未闪退

这种情况对我来说可能是因为exe文件运行较慢,耐心等待即可。

 

5. 打包后黑框中出现警告scrapy15.0,scrapy.contrib.downloadermiddleware.useragent` is deprecated

在我的scrapy中并未修改下载器中间件,虽然出现了此条警告,但是调试好其他错误后,发现exe程序可正常执行,所以貌似忽略这一警告也并未影响效果。

 

6. 在GUI界面运行爬虫程序时出现AttributeError: Can't get attribute 'crawl' on <module '__main__' (built-in)>

在项目中,原本GUI与爬虫进程的启动函数crawl是统一写在一个函数中的,在未打包时,程序运行是没有问题的,但如果使用pyinstaller打包,涉及到多进程,需要将开启进程的crawl函数放在单独的模块之中。因此这个问题的解决方法是,在GUI的python文件相同的目录下,新建crawl.py文件,将crawl函数所使用到的库与函数复制到这个文件中,然后将这个新文件作为一个模块,重新导入GUI文件,使用方法如下:

import crawl
from multiprocessing import Process

##调用crawl模块中的crawl函数
self.p = Process(target=crawl.crawl, args=(self.Q, self.spider_list, keyword, pages, self.hostname, self.dbname, self.username, self.pwd))

 

7.exe文件中反复运行程序打开多个GUI

好不容易成功运行了exe的GUI,此时发现其中的程序似乎在重复执行,没一会给我打开了n个相同的窗口。在一篇博文中找到了解决方法:

在代码最开始加入如下语句:

from multiprocessing import freeze_support
freeze_support()

 

8. 运行爬虫时selenium错误selenium.common.exceptions.WebDriverException: Message: 'chromedriver' executable needs to be in Path

因为我们需要打包成单个文件,这里我们需要包括chromium浏览器和chrome driver到项目文件中,并在程序中定义其路径,这里需要注意两点,一是需要准确定位打包后运行程序时chromium和chrome driver的位置,二是二者的版本需要匹配。

下载chromium的网址为:

https://download-chromium.appspot.com/

下载chrome driver的网址为:

http://npm.taobao.org/mirrors/chromedriver

在我的程序中,下载了最新版的chromium,在浏览器设置中可以看到其版本:

不幸的是之前我的电脑中的chrome driver都不匹配,但是看镜像网站中最高版本的driver也就到85,抱着试一试的心态下载了85.0.4183.38的Windows driver,结果发现程序可以运行了,由此解决了版本匹配问题,也终于不用再多花时间下载其他版本的chromium了!热泪盈眶……

 

打包过程的简单记录

本文所用到的打包知识参考了专栏教程:《PyInstaller打包实战指南》,作者的讲解相当详细,包括其pyqt5的教程,都是很好的学习材料。

1. 初始打包命令

初始的打包命令其实相当简单:

#单文件打包的命令行命令
pyinstaller -F SPGUI.py

#文件夹打包的命令行命令
pyinstaller  SPGUI.py

需要注意的是记得在py文集的目录下进行打包。

如果你的程序不是特别复杂,推荐单文件模式打包,因为这种情况下我们只需要在成功打包之后,使用dist目录下的可执行文件即可,无论是移动目录或是主机间移植,只需要对一个文件进行操作即可。但如果你的文件依赖较多,或是单文件打包后文件较大、打开又比较慢的话,还是使用文件夹打包的方式更加合适,因为在单文件打包的情况下,每次执行exe文件,都会建立一个临时文件夹用来存放程序运行所需的内容,在打包内容比较多的情况下,建立临时文件夹会大大拖慢程序的启动。亲身测试,文件夹打包模式确实很大程度上提高了我的exe文件的打开速度。

 

2. 进阶打包命令——在命令中添加打包资源文件

上面的打包命令是pyinstaller最主要的命令语句,但它的主要作用是将python各种依赖库进行打包,如果我们的程序中需要用到GUI窗口上的图标、scrapy项目中我们自己编写的爬虫程序、selenium中用到的chrome浏览器以及driver,都需要我们主动在命令中将这些内容打包进去,使得exe程序运行时也能够轻松找到这些资源。这里命令需要加入的参数形式为:

--add-data=icon.ico;dir

“--add-data=”表示我们需要添加打包资源文件,相关资源的表示形式是“资源名;目录名”,资源名和目录名用分号分隔,这里目录名就是指exe文件运行目录下的相对路径,例如,在上面的命令添加资源后,我们可以在‘运行目录/dir'路径下找到icon.ico文件。

首先贴上我的文件夹目录:

                                      

如上,两张图分别是SPGUI.py所在的目录,以及该目录的子目录baiduCrawler下的文件结构,其实整个目录就源自于一个scrapy的项目目录。用黄色荧光笔标出来的是我们待打包的内容,其他文件除了dist,build目录以及SPGUI.spec是pyinstaller运行之后生成的,别的都不重要,忽略不计。

下面我们来看一下我最终可行的打包命令:

pyinstaller  -i ./icon3.ico --add-data=start2.png;. --add-data=icon2.ico;. --add-data=mime.types;scrapy --add-data=VERSION;scrapy --add-data=baiduCrawler/*py;baiduCrawler --add-data=baiduCrawler/spiders/*.py;baiduCrawler/spiders --add-data=baiduCrawler/chrome-win.zip;baiduCrawler --add-data=baiduCrawler/chromedriver.exe;baiduCrawler  --runtime-hook=generate_cfg.py SPGUI.py

接下来逐个解释一下参数的含义:

“-i ./icon3.ico”的目的是设置exe文件的图标,这样我们exe文件生成之后图标就不会丑得抱歉了;

“--add-data=start2.png;. --add-data=icon2.ico;.”此处添加的是我在pyqt5中用到的GUI界面的启动画面以及窗口图标,分号后面的文件夹“.”表示的就是exe运行的根目录,单文件打包时这个目录就是临时文件目录,文件夹打包时也就是exe的当前目录。

“--add-data=mime.types;scrapy --add-data=VERSION;scrapy --add-data=baiduCrawler/*py;baiduCrawler --add-data=baiduCrawler/spiders/*.py;baiduCrawler/spiders” 这些添加的资源是用于scrapy的,其中mime.types以及VERSION文件我们首先需要从python的scrapy安装包中拷贝到当前的目录下。后面两句add-data则是将baiduCrawler目录下的py文件添加到运行目录的“baiduCrawler”以及“baiduCrawler/spiders”目录下。这几条保证了scrapy的正常运行。

“--add-data=baiduCrawler/chrome-win.zip;baiduCrawler --add-data=baiduCrawler/chromedriver.exe;baiduCrawler” 是用于支持selenium正常运行的,chromium浏览器压缩包和chrome driver是我们提前准备好在baiduCrawler目录下的,这里也就是原样移动到了exe的可运行目录。

“--runtime-hook=generate_cfg.py” 是为了添加scrapy的运行钩子,generate_cfg,py是一个简单的文件生成代码,作用是在exe文件同目录生成scrapy.cfg文件。

 

3. 打包时对原有py文件的改动

打包时,除了前面问题中提到的crawl函数的模块化,其他需要对我们原本py程序进行的改动主要集中在SPGUI.py中python库的引入,以及selenium使用时,对chrome相关文件路径的解析。

为了避免生成exe之后,缺乏某个python库的情况,我们需要把用到的库都导入到用于生成exe文件的SPGUI.py中,包括我们在其他py文件中使用到的库,只有在SPGUI中导入了,才不会缺少。这里贴一下我最终导入的部分:

from multiprocessing import freeze_support
freeze_support()
import sys
from PyQt5.QtGui import QIcon, QPixmap
from PyQt5.QtCore import Qt, pyqtSlot
from PyQt5.QtSql import QSqlDatabase, QSqlTableModel, QSqlQueryModel, QSqlQuery
from PyQt5.QtWidgets import QApplication, QMessageBox, QTableView, QWidget, QLabel, QVBoxLayout, QHBoxLayout, \
    QHeaderView, QLineEdit, QPushButton, QCheckBox, QGridLayout, QComboBox, QTabWidget, QSpinBox, QSplitter, QDialog,\
    QDialogButtonBox, QSplashScreen
from PyQt5.QtCore import QThread, pyqtSignal, QFile, QTextStream
from baiduCrawler.spiders.tmp import TmpSpider
from baiduCrawler.spiders.baijiahao import  BaijiahaoSpider
from baiduCrawler.spiders.sinanews import SinanewsSpider
from multiprocessing import Process, Manager, JoinableQueue
import ctypes
import scrapy
from scrapy.utils.project import get_project_settings
from scrapy.crawler import CrawlerProcess
import logging
import os
import configparser
import pymysql
import crawl
import scrapy.spiderloader
import scrapy.statscollectors
import scrapy.logformatter
import scrapy.dupefilters
import scrapy.squeues

import scrapy.extensions.spiderstate
import scrapy.extensions.corestats
import scrapy.extensions.telnet
import scrapy.extensions.logstats
import scrapy.extensions.memusage
import scrapy.extensions.memdebug
import scrapy.extensions.feedexport
import scrapy.extensions.closespider
import scrapy.extensions.debug
import scrapy.extensions.httpcache
import scrapy.extensions.statsmailer
import scrapy.extensions.throttle
import scrapy.downloadermiddlewares.chunked
import scrapy.core.scheduler
import scrapy.core.engine
import scrapy.core.scraper
import scrapy.core.spidermw
import scrapy.core.downloader
import selenium
import scrapy.downloadermiddlewares.stats
import scrapy.downloadermiddlewares.httpcache
import scrapy.downloadermiddlewares.cookies
import scrapy.downloadermiddlewares.useragent
import scrapy.downloadermiddlewares.httpproxy
import scrapy.downloadermiddlewares.ajaxcrawl
import scrapy.downloadermiddlewares.chunked
import scrapy.downloadermiddlewares.decompression
import scrapy.downloadermiddlewares.defaultheaders
import scrapy.downloadermiddlewares.downloadtimeout
import scrapy.downloadermiddlewares.httpauth
import scrapy.downloadermiddlewares.httpcompression
import scrapy.downloadermiddlewares.redirect
import scrapy.downloadermiddlewares.retry
import scrapy.downloadermiddlewares.robotstxt

import scrapy.spidermiddlewares.depth
import scrapy.spidermiddlewares.httperror
import scrapy.spidermiddlewares.offsite
import scrapy.spidermiddlewares.referer
import scrapy.spidermiddlewares.urllength

import scrapy.pipelines

import scrapy.core.downloader.handlers.http
import scrapy.core.downloader.contextfactory
from scrapy import signals
import re
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.common.keys import Keys
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.support.ui import WebDriverWait
from selenium.common.exceptions import TimeoutException, NoSuchElementException
from selenium.webdriver.common.action_chains import ActionChains
from scrapy.http import HtmlResponse
import time
import zipfile

需要导入的模块因人而异,如果只使用了scrapy,那么导入以scrapy开头的库应该也够用了。

接下来是使用selenium时chrome路径的解析。首先我们定义一个获取资源路径的函数:

    def res_path(self, relative_path):
        """获取资源绝对路径"""
        try:
            base_path = sys._MEIPASS
        except Exception:
            base_path = os.path.abspath(".")

        return os.path.join(base_path, relative_path)

其含义是尝试获取文件的临时目录,如果不存在,则获取文件的绝对路径。然后,对chromium压缩包解压缩,并且导入driver文件,最后调出浏览器:

if not os.path.exists(self.res_path('chrome-win')):
    # 先将zip压缩文件进行解压
    chrome_zip = zipfile.ZipFile(self.res_path('baiduCrawler/chrome-win.zip'))
    chrome_zip.extractall(self.res_path('./'))  #解压到当前运行目录

option = webdriver.ChromeOptions()
option.binary_location = self.res_path('chrome-win/chrome.exe')
driver_path = self.res_path('baiduCrawler/chromedriver.exe')
br = webdriver.Chrome(executable_path=driver_path, chrome_options=option)

这其实也为我们提供了一种生成exe后读取文件的方法。

 

4. 其他

以上其实已经为大家介绍了本次打包的主要过程,其他小tips包括:去除pyinstaller自带的调试黑框,去除selenium打包后的黑框。

去除pyinstaller的黑框其实也很简单,只需要在命令中添加“-w”即可。比如,如果我希望去掉上面SPGUI.py的黑框,可以将命令改为:

pyinstaller -w -i ./icon3.ico --add-data=start2.png;. --add-data=icon2.ico;. --add-data=mime.types;scrapy --add-data=VERSION;scrapy --add-data=baiduCrawler/*py;baiduCrawler --add-data=baiduCrawler/spiders/*.py;baiduCrawler --add-data=baiduCrawler/chrome-win.zip;baiduCrawler --add-data=baiduCrawler/chromedriver.exe;baiduCrawler  --runtime-hook=generate_cfg.py SPGUI.py

这个pyinstaller的框框其实很有用,它显示程序运行时的log信息,我们可以方便地看到exe运行过程中出现的错误。如果出现某些错误导致黑框闪退,windows系统下可以使用power shell打开exe文件,shell界面将显示相关错误:

selenium运行时chromedriver.exe也会生成一个黑框,解决方法可以在这篇文章中找到:

https://blog.csdn.net/La_vie_est_belle/article/details/81252588

 

好啦,使用pyinstaller对python程序打包过程的记录就到这里了,到此,爬虫程序的编写+打包基本完成,接下来打算写一篇关于图形界面的制作的记录,此后我还将对爬虫收集信息进行正式的处理以及可视化,相信到时还需要再次用到pyinstaller。希望自己可以在这个过程中不断学习,不断实践!

  • 2
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值