鱼C论坛

 找回密码
 立即注册
查看: 3131|回复: 16

[已解决]线程池正常,改为进程池就报错,求多进程大神解答一下

[复制链接]
发表于 2021-11-15 15:04:26 | 显示全部楼层 |阅读模式

马上注册,结交更多好友,享用更多功能^_^

您需要 登录 才可以下载或查看,没有账号?立即注册

x
代码比较多, 这里就节选了一点说明,大家都知道ThreadPoolExecutor和ProcessPoolExecutor用法几乎是一致的。
然后我就把下面的多线程ThreadPoolExecutor()更改为多进程ProcessPoolExecutor(),但是就直接报错了。
错误代码为:RecursionError: maximum recursion depth exceeded while getting the str of an object

问题:我并没有写递归函数,仅仅是爬取了250个电源的排名信息,为什么会报递归超过深度错误呢?  如果要修改,应该如何修改呢,跪求大神!


多线程代码:
...这里有很多代码
    def many_thread(self):
        with ThreadPoolExecutor() as pool:             #-----本身是正常运行的,这里更改为ProcessPoolExecutor就会报错!----------
            jiexi_html = pool.map(self.x_get_html,self.url_list)  #获取每一页的解析页面列表
            result = pool.map(self.get_everypage_info,jiexi_html) #获取每一页页面的电影信息
        for i in result:
            for a in i:
                print(a,end='\n')
....这里也有很多代码

多进程代码:
其他都没有更改,就更改了下面一处。
    def many_thread(self):
        with ProcessPoolExecutor() as pool:             #-----本身是正常运行的,这里更改为ProcessPoolExecutor就会报错!----------
            jiexi_html = pool.map(self.x_get_html,self.url_list)  #获取每一页的解析页面列表
            result = pool.map(self.get_everypage_info,jiexi_html) #获取每一页页面的电影信息
        for i in result:
            for a in i:
                print(a,end='\n')
最佳答案
2021-11-15 15:12:30
使用多进程后,你的程序入口必须要置于if __name__ == "__main__"分支下才能保证创建新进程后不会发生递归。
例如:
# any code
# ...

def main():
    # 入口
    ...

if __name__ == "__main__":
    main()
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复

使用道具 举报

发表于 2021-11-15 15:12:30 | 显示全部楼层    本楼为最佳答案   
使用多进程后,你的程序入口必须要置于if __name__ == "__main__"分支下才能保证创建新进程后不会发生递归。
例如:
# any code
# ...

def main():
    # 入口
    ...

if __name__ == "__main__":
    main()
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

发表于 2021-11-15 21:29:51 | 显示全部楼层
因为你是windows系统 Linux铁定不报错
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

 楼主| 发表于 2021-11-15 21:47:46 | 显示全部楼层
hrpzcf 发表于 2021-11-15 15:12
使用多进程后,你的程序入口必须要置于if __name__ == "__main__"分支下才能保证创建新进程后不会发生递归 ...

我是这样写的哦,
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

发表于 2021-11-15 22:01:12 From FishC Mobile | 显示全部楼层
从入门到富豪 发表于 2021-11-15 21:47
我是这样写的哦,


没有全代码不好分析,总之在windows上创建新进程是要重新导入整个文件,如果程序入口不在name==main分支下,文件会被再次运行,再次创建新进程,再次导入,再次运行...无限递归,你从这方面分析一下你的程序吧,看看有没有零散的语句在导入的时候直接被执行的,把它们都包装成函数或者其他,再定义一个唯一入口放在name==main下。
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

 楼主| 发表于 2021-11-17 10:10:37 | 显示全部楼层
hrpzcf 发表于 2021-11-15 22:01
没有全代码不好分析,总之在windows上创建新进程是要重新导入整个文件,如果程序入口不在name==main分 ...

非常感谢你的答复,我附上完整的代码。的确都是写在 name==main下面的。
import requests
import time
from concurrent.futures import ThreadPoolExecutor,ProcessPoolExecutor
from bs4 import BeautifulSoup
from fake_useragent import UserAgent
class Get_Movie():
    def __init__(self,url_list):
        self.url_list = url_list
        pass
    #获取网页,并解析网页为text模式,通用函数前面加x
    def x_get_html(self,url):
        try:
            # header = {'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.90 Safari/537.36'}
            ua = UserAgent(verify_ssl=False)
            header = {"User-Agent": ua.random}
            requests.packages.urllib3.disable_warnings()
            response = requests.get(url,verify=False,headers=header)
            if response.status_code != 200:
                raise Exception('链接网页服务器不成功')
            else:
                response.encoding = response.apparent_encoding  # 获取网页字体编码,防止乱码
                html = response.text
                soup = BeautifulSoup(html,'lxml')  #解析一个网页
                return soup
        except:
            print('网页不存在')

    def get_everypage_info(self,x):
        everyone_moive = x.find('ol',class_='grid_view').find_all('div',class_='item')
        a = [(i.find('em').string,i.find('span',class_='title').string,i.find('span',class_='rating_num').string) for i in everyone_moive]
        return a

    def many_thread(self):
        with ProcessPoolExecutor() as pool:
            jiexi_html = pool.map(self.x_get_html,self.url_list)  #获取每一页的解析页面列表
            result = pool.map(self.get_everypage_info,jiexi_html) #获取每一页页面的电影信息
        for i in result:
            for a in i:
                print(a,end='\n')
                # print(f'{i}+\n')
if __name__=='__main__':
    start = time.process_time()
    getmovie = Get_Movie([f'https://movie.douban.com/top250?start={x}&filter=' for x in range(0,250,25)])
    getmovie.many_thread()
    end = time.process_time()
    print(f'一共用时:{end-start}秒')
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

发表于 2021-11-17 12:03:44 | 显示全部楼层
从入门到富豪 发表于 2021-11-17 10:10
非常感谢你的答复,我附上完整的代码。的确都是写在 name==main下面的。
import requests
import time
...


因为进程的返回值要求是可序列化的对象(你的返回值是列表套元组),把它改正就好(字符串列表),为了方便我把他合成一个函数了。
import requests
import time
from concurrent.futures import ThreadPoolExecutor, ProcessPoolExecutor
from bs4 import BeautifulSoup
from fake_useragent import UserAgent


class Get_Movie:
    def __init__(self, url_list):
        self.url_list = url_list
        pass

    # 获取网页,并解析网页为text模式,通用函数前面加x
    def x_get_html(self, url):
        try:
            # header = {'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.90 Safari/537.36'}
            ua = UserAgent(verify_ssl=False)
            header = {"User-Agent": ua.random}
            requests.packages.urllib3.disable_warnings()
            response = requests.get(url, verify=False, headers=header)
            if response.status_code != 200:
                raise Exception("链接网页服务器不成功")
            else:
                response.encoding = response.apparent_encoding  # 获取网页字体编码,防止乱码
                html = response.text
                soup = BeautifulSoup(html, "lxml")  # 解析一个网页
                return self.get_everypage_info(soup)
        except:
            print("网页不存在")

    def get_everypage_info(self, x):
        everyone_moive = x.find("ol", class_="grid_view").find_all("div", class_="item")
        a = [
            ", ".join(
                (i.find("em").string,
                i.find("span", class_="title").string,
                i.find("span", class_="rating_num").string)
            )
            for i in everyone_moive
        ]
        return a

    def many_thread(self):
        with ProcessPoolExecutor() as pool:
            jiexi_html = pool.map(self.x_get_html, self.url_list)  # 获取每一页的解析页面列表
            # result = pool.map(self.get_everypage_info, jiexi_html)  # 获取每一页页面的电影信息
        for i in jiexi_html:
            for a in i:
                print(a, end="\n")
                # print(f'{i}+\n')


if __name__ == "__main__":
    start = time.perf_counter() # 用这个才是秒数
    getmovie = Get_Movie(
        [
            f"https://movie.douban.com/top250?start={x}&filter="
            for x in range(0, 250, 25)
        ]
    )
    getmovie.many_thread()
    end = time.perf_counter()
    print(f"一共用时:{end-start}秒")
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

 楼主| 发表于 2021-11-17 16:10:58 | 显示全部楼层
hrpzcf 发表于 2021-11-17 12:03
因为进程的返回值要求是可序列化的对象(你的返回值是列表套元组),把它改正就好(字符串列表),为了 ...

感谢大神,能运行了,但是我对
进程的返回值 需要是 可序列化对象,这个不太理解。 也没有找到相关对进程返回值相关的要求的文献,方便详细指导一下吗,  非常感谢!
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

发表于 2021-11-17 16:23:31 | 显示全部楼层
从入门到富豪 发表于 2021-11-17 16:10
感谢大神,能运行了,但是我对
进程的返回值 需要是 可序列化对象,这个不太理解。 也没有找到相关对进 ...

https://docs.python.org/zh-cn/3. ... ProcessPoolExecutor
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

发表于 2021-11-17 16:27:15 | 显示全部楼层
从入门到富豪 发表于 2021-11-17 16:10
感谢大神,能运行了,但是我对
进程的返回值 需要是 可序列化对象,这个不太理解。 也没有找到相关对进 ...

https://www.jianshu.com/p/53c2e732d974
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

 楼主| 发表于 2021-11-17 22:34:48 | 显示全部楼层
hrpzcf 发表于 2021-11-17 16:27
https://www.jianshu.com/p/53c2e732d974

大神, 我看了3遍了, 感觉是匹配下面这句话,但是我进程间并不需要通信啊, 也没有通信呀。
函数参数和返回值必须兼容pickle,因为要使用到进程间的通信,所有解释器之间的交换数据必须被序列化
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

 楼主| 发表于 2021-11-17 22:42:46 | 显示全部楼层
从入门到富豪 发表于 2021-11-17 22:34
大神, 我看了3遍了, 感觉是匹配下面这句话,但是我进程间并不需要通信啊, 也没有通信呀。
函数参数 ...

列表套元组也是序列化的一种呀,为啥就不行了呢? 而且弹出的提示是超出迭代深度呀。 是不是内容迭代的内容太多了。
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

发表于 2021-11-17 23:54:38 From FishC Mobile | 显示全部楼层
从入门到富豪 发表于 2021-11-17 22:34
大神, 我看了3遍了, 感觉是匹配下面这句话,但是我进程间并不需要通信啊, 也没有通信呀。
函数参数 ...

子进程把函数结果返回到主进程不是进程间通信?至于嵌套元组的列表为什么不行我也不了解其原理,按理说是可以被序列化的。
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

发表于 2021-11-17 23:59:03 From FishC Mobile | 显示全部楼层
从入门到富豪 发表于 2021-11-17 22:42
列表套元组也是序列化的一种呀,为啥就不行了呢? 而且弹出的提示是超出迭代深度呀。 是不是内容迭代的内 ...

按理说是不会发生递归的,但它就是发生了。只要发生递归而且没有出口,那肯定是会超出最大深度的。
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

 楼主| 发表于 2021-11-18 10:13:44 | 显示全部楼层
hrpzcf 发表于 2021-11-17 23:59
按理说是不会发生递归的,但它就是发生了。只要发生递归而且没有出口,那肯定是会超出最大深度的。

非常感谢, 我再试试, 看能不能挖掘出啥信息。挖到了这里答复哈,
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

 楼主| 发表于 2021-11-18 10:17:42 | 显示全部楼层
从入门到富豪 发表于 2021-11-18 10:13
非常感谢, 我再试试, 看能不能挖掘出啥信息。挖到了这里答复哈,

我刚刚尝试了一下, 把第二行用多线程, 还是会报错递归超过深度, 那就感觉是第一行代码的问题了。
    def many_thread(self):
        with ProcessPoolExecutor() as pool:
            jiexi_html = pool.map(self.x_get_html,self.url_list)  #获取每一页的解析页面列表
        with ThreadPoolExecutor() as pool:
            result = pool.map(self.get_everypage_info,jiexi_html) #获取每一页页面的电影信息
        for i in result:
            for a in i:
                print(a,end='\n')
                # print(f'{i}+\n')
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

 楼主| 发表于 2021-11-19 16:13:12 | 显示全部楼层
hrpzcf 发表于 2021-11-17 23:54
子进程把函数结果返回到主进程不是进程间通信?至于嵌套元组的列表为什么不行我也不了解其原理,按理说是 ...

测试成功了, 不用这样写也行 ,多进程会传递空值给到函数,导致连环报错,但是不知道为什么会导致传递空值给函数。 加了一个if语句过滤了一下。
  def many_thread(self):
        with ProcessPoolExecutor() as pool:  #标记点1
            jiexi_html = pool.map(self.x_get_html, self.url_list)  # 获取每一页的解析页面列表
        with ProcessPoolExecutor() as pool1: #标记点2 这一步是负优化,仅仅用来测试 因为数据已经get到本地了。直接单线程
        #     #就可以搞定了,这里使用多线程能正常运行,使用多进程报错:AttributeError: 'NoneType' object has no attribute 'picklable'
        #     #推测报错原因是有函数或者类无法被序列化。所以无法进入进程池。None的类型就是NoneType,所以没有序列化成功,没有东西进入进程池。
            result = pool1.map(self.get_everypage_info, jiexi_html)  # 获取每一页页面的电影信息
        print(result)


过滤的if语句
    def get_everypage_info(self, x):
        if x != None: #写这句话,是因为多进程map函数,有时候有传值None进来。
            everyone_moive = x.find('ol', class_='grid_view').find_all('div', class_='item')
            jieguo1 = [[i.find('em').string, i.find('span', class_='title').string, i.find('span', class_='rating_num').string]
                for i in everyone_moive]
            return jieguo1
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|手机版|Archiver|鱼C工作室 ( 粤ICP备18085999号-1 | 粤公网安备 44051102000585号)

GMT+8, 2025-1-12 18:59

Powered by Discuz! X3.4

© 2001-2023 Discuz! Team.

快速回复 返回顶部 返回列表