安装pyinstaller报错_使用 【pyinstaller】 打包 【scrapy】项目

0ebabdfc406c89c1ce011d3d2c6cd86e.png

Using pyinstaller to package scrapy project into exetutable file

目标:将scrapy项目打包成二进制

方法:调用第三方库 pyinstaller

基本原理:pyinstaller在打包python文件成二进制文件时,将收集执行python文件所需要的库以及解释器,然后将其封装起来成一个整体。在这种方式下,打包好的文件则可以直接在其他电脑上运行,不要其安装相关库以及python。

pyinstaller打包缺点:

  • 在windows平台上打包的文件只能在windows上运行;在linius平台上打包的文件只能在linius平台运行;
  • 由于pyisntaller在python文件时,是根据文件中的import语句来手机相关库,如果存在动态导入,例如在函数中进行导入或者使用importlib进行导入,pyinstaller都将无法导入;
  • 如果相关库需要一些数据文件,由于数据文件的导入不是使用import语句,将使得相关数据文件无法被打包,这时候需要告诉pyinstaller数据文件的位置;

案例:使用官网quotes案例

pyinstaller打包scapy的有三个关键点:

  • 目录,即文件的结构
  • 爬虫的入口
  • 打包的方式

先来看看目录

==目录==

1,未打包之前的目录

f92986f77c9f15d3a20a4b18eda25e80.png
未打包之前的目录

2,打包之后的目录

7910764e8361e2ab8fce3db03ab91dd9.png
打包之后的目录

可以看出,这里多个两个文件夹:【dist】【build】

【dist】文件主要运行程序所需要的部件以及可执行程序,【build】文件夹主要存放日志以及一些工作文件。

注意:run.py放在工程目录下面,一方面可以让scrapy添加寻址路径;另外一方面不会存在工作目录下相对导入的问题;

==爬虫入口==

run文件内容

#coding = utf-8
from scrapy.crawler import CrawlerProcess
from scrapy.utils.project import get_project_settings
# -*- coding: utf-8 -*-
import scrapy
from quote.items import  QuoteItem
from  scrapy.http.response.html import  HtmlResponse as HtmlResponse
from  scrapy.http.request import Request

#解决方案--官网
class QuotesSpider(scrapy.Spider):
    name = 'quotes'
    allowed_domains = ['quotes.toscrape.com']   #过滤器
    start_urls = ['http://quotes.toscrape.com/']

    def parse(self, response):
        assert isinstance(response,HtmlResponse)
        urls = response.xpath("//div[@class='col-md-4 tags-box']//a/@href").extract()
        tags = response.xpath("//div[@class='col-md-4 tags-box']//a/text()").extract()
        abs_urls = [response.url[:-1] + i  for i in urls]
        print("abs_urls",abs_urls)
        for i,j,p in zip(abs_urls,tags,range(len(tags))):
            yield Request(url= i,meta={"base_tag":j},priority=p,callback=self.parse_tag)
    def parse_tag(self,response):
        item = QuoteItem()
        assert isinstance(response,HtmlResponse)
        bases = response.xpath("//div[@class='col-md-8']/div[@class='quote']")
        for base in bases:
            item["text"] = base.xpath("./span/text()").extract_first() #quote
            item["author"] = base.xpath("./span/text()").extract()[1] #author
            item["tags"] = base.xpath(".//div[@class ='tags']/a/text()").extract()
            item["base_tag"] = response.request.meta["base_tag"]
            item["priority"] = str(response.request.priority)
            yield  item
        next_page_text = response.xpath("//ul[@class='pager']/li/a/text()").extract_first()  #next page
        print("next_page_text",next_page_text)
        if 'Next' in next_page_text:
            url = response.xpath("//ul[@class='pager']/li/a/@href").extract_first()
            next_page = 'http://quotes.toscrape.com' + url
            base_tag = url.split("/")[2] if url.split("/")[2]  else ""
            print("next_page",next_page)
            yield Request(url= next_page,priority=response.request.priority,callback=self.parse_tag,meta={"base_tag":base_tag})

#创建一个进程
process = CrawlerProcess(get_project_settings())
# 'followall' is the name of one of the spiders of the project.
process.crawl(QuotesSpider)  #避开命令行
process.start()

重点:

1,程序的入口,没有采用常规的cmdline形式,而是因为scrapy commands文件夹的下面命令在导入是实在函数中进行,pyinstaller无法找到,除非你告诉它位置,否则报错无法找到命令。以下是采用cmdline.execute的核心机制,从中可以看出命令的加载机制。

def execute(argv=None, settings=None):
    #核心函数
	#导入设置;取出命令;运行命令
    if argv is None:
        argv = sys.argv     #sys.argv[]是用来获取命令行参数;列表

    # --- backwards compatibility for scrapy.conf.settings singleton ---
    if settings is None and 'scrapy.conf' in sys.modules:
        from scrapy import conf
        if hasattr(conf, 'settings'):
            settings = conf.settings    #获取设置
    # ------------------------------------------------------------------

    if settings is None:
        settings = get_project_settings()
        # set EDITOR from environment if available
        try:
            editor = os.environ['EDITOR']
        except KeyError: pass
        else:
            settings['EDITOR'] = editor
    check_deprecated_settings(settings)  #编辑器

    # --- backwards compatibility for scrapy.conf.settings singleton ---
    import warnings
    from scrapy.exceptions import ScrapyDeprecationWarning
    with warnings.catch_warnings():
        warnings.simplefilter("ignore", ScrapyDeprecationWarning)
        from scrapy import conf
        conf.settings = settings
    # ------------------------------------------------------------------

    inproject = inside_project()   #true or false
	
	
	#==================cmds的获取方式==================
    cmds = _get_commands_dict(settings, inproject)
	#================================================
	
	
	
    cmdname = _pop_command_name(argv)  #取出命令的名称
    parser = optparse.OptionParser(formatter=optparse.TitledHelpFormatter(), 
        conflict_handler='resolve')
    if not cmdname:
        _print_commands(settings, inproject)
        sys.exit(0)
    elif cmdname not in cmds:
        _print_unknown_command(settings, cmdname, inproject)
        sys.exit(2)

    cmd = cmds[cmdname]#取出命令
    parser.usage = "scrapy %s %s" % (cmdname, cmd.syntax())
    parser.description = cmd.long_desc()
    settings.setdict(cmd.default_settings, priority='command')
    cmd.settings = settings  
    cmd.add_options(parser)  #加解析器
    #解析命令行参数
    opts, args = parser.parse_args(args=argv[1:])  #arg:未知参数;opts,命名参数
    _run_print_help(parser, cmd.process_options, args, opts)    #处理opts

    cmd.crawler_process = CrawlerProcess(settings)#核心,简历crawl流程
    _run_print_help(parser, _run_command, cmd, args, opts)  #运行命令
    sys.exit(cmd.exitcode)#运行结束,返回状态码

def _get_commands_dict(settings, inproject):
    cmds = _get_commands_from_module('scrapy.commands', inproject)
    cmds.update(_get_commands_from_entry_points(inproject))#更新命令
    cmds_module = settings['COMMANDS_MODULE']    #定制的命令;自行定义命令的模块
    if cmds_module:
        cmds.update(_get_commands_from_module(cmds_module, inproject))
    return cmd

明显,_get_commands_from_module('scrapy.commands', inproject)是指从模块中加载文件。由于调用的函数较多,这里就不一一阐述其工作机制。

2,process.crawl(QuotesSpider),直接写爬虫的类,不要写名字

道理和上面的一样,spider所在的模块也是在在函数中导入

==pyinstaller的打包方法==

一般的打包,只需要直接运行 pyinstaller xx.py 即可,如果有需要,则添加一些参数,具体请查看官网。

而scrapy存在大量的隐形导入(这里也包括部分数据文件),pyinstaller 无法知道这些文件需要被导入,因此,打包之后的程序无法运行。

解决方案--

1,缺啥补啥。在运行打包之后的程序时,提示无法找到某个文件,则在run.py 直接导入。具体可以看 《用Pyinstaller打包Scrapy项目问题解决!!! - CSDN博客》

2,构建hook文件,系统性的导入文件。这里分为两步,第一步,构建scrapy-hook.py文件;第二步,构建run.spec文件。

scrapy-hook.py

#coding=utf-8
#调用hook,批量导入数据与模块
from PyInstaller.utils.hooks import collect_submodules, collect_data_files

# This collects all dynamically imported scrapy modules and data files.
hiddenimports = (collect_submodules('scrapy') +
                 collect_submodules('scrapy.pipelines') +
                 collect_submodules('scrapy.extensions') +
                 collect_submodules('scrapy.utils')+collect_submodules('scrapy.spiders')
)
#加载数据
datas = collect_data_files('scrapy')

hiddenimports告诉pyinstaller有哪些隐形导入;datas告诉有哪些数据需要添加。

collect_submodules, collect_data_files这两个函数为我们提供统一导入模块与数据的接口,适合系统性的解决打包问题,这也是hooK文件由来--专门为解决某一个库的打包问题。

run.spec

# -*- mode: python -*-

block_cipher = None


a = Analysis(['run.py'],
             pathex=['D:quote_pyinstaller'],
             binaries=[],
             datas=[(".scrapy.cfg",".")],
             hiddenimports=["quote.items","quote.middlewares",'quote.pipelines','quote.settings','quote.spiders'],
             hookspath=[".hooks"],
             runtime_hooks=[],
             excludes=[],
             win_no_prefer_redirects=False,
             win_private_assemblies=False,
             cipher=block_cipher)
pyz = PYZ(a.pure, a.zipped_data,
             cipher=block_cipher)
exe = EXE(pyz,
          a.scripts,
          exclude_binaries=True,
          name='run',
          debug=False,
          strip=False,
          upx=True,
          console=True )
coll = COLLECT(exe,
               a.binaries,
               a.zipfiles,
               a.datas,
               strip=False,
               upx=True,
               name='run')

这个文件有两个要点:hiddenimports 与 hookspath

  • hiddenimports告诉pyinstaller有哪些文件需要被导入;
  • hookspath告诉pyinstaller hook文件的位置

==================================================

生成可执行文件时,使用命令 pyinstaller run.spec

后记:scrapy的打包这个问题,困扰了我好几天。一开始以为一个简单的pyinstaller run.py就可以搞定一切,结果打包的程序不断被报错;在阅读pyinstaller官网以及scrapy源码之后,才找到避开一些坑的路子。由此,要搞定一些问题,还得多花时间,系统性思考。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值