一、简介

  为了提高爬虫程序效率,由于python解释器GIL,导致同一进程中即使有多个线程,实际上也只会有一个线程在运行,但通过request.get发送请求获取响应时有阻塞,所以采用了多线程依然可以提高爬虫效率。

多线程爬虫注意点
1.解耦

  整个程序分为4部分,url list模块、发送请求,获取响应模块、数据提取模块、保存模块,如果某一模块出现问题,互相之间不会影响。

2. 资源竞争

  由于使用了多线程,不同线程在共享数据时,容易产生资源竞争,假设共享数据放入列表中,那么同一时刻有可能2个线程去列表中取同一个数据,重复使用。解决办法是使用队列,使得某一线程get数据时,其他线程无法get同一数据,真正起到保护作用,类似互斥锁。

队列常用方法介绍

  1. from queue import Queue
  2.  
  3. q = Queue()
  4. q.put(url)
  5. q.get() # 当队列为空时,阻塞
  6. q.empty() # 判断队列是否为空,True/False

注意:

  • get和get_nowait两者的区别是当队列取完了即队列为空时,get()会阻塞,等待着新数据继续取,而get_nowait()会报错;
  • put和put_nowait 两者的区别是当队列为满时,put_nowait()会报错;

队列其他方法join task_done setDaemon

  • 在python3中,join()会等待子线程、子进程结束之后,主线程、主进程才会结束.
  • 队列中put队列计数会+1,get时计数不会减1,但当get+task_done时,队列计数才会减1,如果没有task_done则程序跑到最后不会终止。task_done()的位置,应该放在方法的最后以保证所有任务全部完成.
  • setDaemon方法把子线程设置为守护线程,即认为该方法不是很重要,记住主线程结束,则该子线程结束
  • join方法和setDaemon方法搭配使用。主线程进行到join()处,join的效果是让主线程阻塞,等待子线程中队列任务完成之后再解阻塞,等子线程结束,join效果失效,之后主线程结束,由于使用了setDaemon(True),所以子线程跟着结束,此时整个程序结束。

 

线程模块

  1. from threading import Thread

  2. # 使用流程
  3. t = Thread(target=函数名) # 创建线程对象
  4. t.start() # 创建并启动线程
  5. t.join() # 阻塞等待回收线程

应用场景

  • 多进程 :CPU密集程序
  • 多线程 :爬虫(网络I/O)、本地磁盘I/O

二、案例

1. 小米应用商店抓取

目标

  1. 网址 :百度搜 - 小米应用商店,进入官网,应用分类 - 聊天社交
  2. 目标 :爬取应用名称和应用链接

实现步骤

1、确认是否为动态加载:页面局部刷新,查看网页源代码,搜索关键字未搜到,因此此网站为动态加载网站,需要抓取网络数据包分析

2、抓取网络数据包

  • 抓取返回json数据的URL地址(Headers中的Request URL)http://app.mi.com/categotyAllListApi?page={}&categoryId=2&pageSize=30
  • 查看并分析查询参数(headers中的Query String Parameters)只有page在变,0 1 2 3 ... ... ,这样我们就可以通过控制page的值拼接多个返回json数据的URL地址
  1. page: 1
  2. categoryId: 2
  3. pageSize: 30

3、将抓取数据保存到csv文件。注意多线程写入的线程锁问题

  1. from threading import Lock
  2.  
  3. lock = Lock()
  4. lock.acquire()
  5. lock.release()

整体实现思路

  1. 在 __init__(self) 中创建文件对象,多线程操作此对象进行文件写入;
  2. 每个线程抓取数据后将数据进行文件写入,写入文件时需要加锁;
  3. 所有数据抓取完成关闭文件;
  1. import requests
  2. from threading import Thread
  3. from queue import Queue
  4. import time
  5. from lxml import etree
  6. import csv
  7. from threading import Lock
  8.  
  9. class XiaomiSpider(object):
  10. def __init__(self):
  11. self.url = 'http://app.mi.com/categotyAllListApi?page={}&categoryId={}&pageSize=30'
  12. self.ua = {'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/14.0.835.163 Safari/535.1'}
  13. self.q = Queue() # 存放所有URL地址的队列
  14. self.i = 0
  15. self.id_list = [] # 存放所有类型id的空列表
  16. # 打开文件
  17. self.f = open('xiaomi.csv', 'a', newline="")
  18. self.writer = csv.writer(self.f)
  19. self.lock = Lock() # 创建锁
  20.  
  21. def get_cateid(self):
  22. url = 'http://app.mi.com/'
  23. html = requests.get(url=url, headers=self.ua).text
  24.  
  25. parse_html = etree.HTML(html)
  26. li_list = parse_html.xpath('//ul[@class="category-list"]/li')
  27. for li in li_list:
  28. typ_name = li.xpath('./a/text()')[0]
  29. typ_id = li.xpath('./a/@href')[0].split('/')[-1]
  30. pages = self.get_pages(typ_id) # 计算每个类型的页数
  31. self.id_list.append((typ_id, pages))
  32.  
  33. self.url_in() # 入队列
  34.  
  35. def get_pages(self, typ_id):
  36. # 每页返回的json数据中,都有count这个key
  37. url = self.url.format(0, typ_id)
  38. html = requests.get(url=url, headers=self.ua).json()
  39. count = html['count'] # 类别中的数据总数
  40. pages = int(count) // 30 + 1 # 每页30个,看有多少页
  41.  
  42. return pages
  43.  
  44. # url入队列
  45. def url_in(self):
  46. for id in self.id_list:
  47. # id为元组,(typ_id, pages)-->('2',pages)
  48. for page in range(2):
  49. url = self.url.format(page, id[0])
  50. print(url)
  51. # 把URL地址入队列
  52. self.q.put(url)
  53.  
  54. # 线程事件函数: get() - 请求 - 解析 - 处理数据
  55. def get_data(self):
  56. while True:
  57. # 当队列不为空时,获取url地址
  58. if not self.q.empty():
  59. url = self.q.get()
  60. html = requests.get(url=url, headers=self.ua).json()
  61. self.parse_html(html)
  62. else:
  63. break
  64.  
  65. # 解析函数
  66. def parse_html(self, html):
  67. # 存放1页的数据 - 写入到csv文件
  68. app_list = []
  69. for app in html['data']:
  70. # 应用名称 + 链接 + 分类
  71. name = app['displayName']
  72. link = 'http://app.mi.com/details?id=' + app['packageName']
  73. typ_name = app['level1CategoryName']
  74. # 把每一条数据放到app_list中,目的为了 writerows()
  75. app_list.append([name, typ_name, link])
  76. print(name, typ_name)
  77. self.i += 1
  78.  
  79. # 开始写入1页数据 - app_list
  80. self.lock.acquire()
  81. self.writer.writerows(app_list)
  82. self.lock.release()
  83. # 主函数
  84. def main(self):
  85. self.get_cateid() # URL入队列
  86. t_list = []
  87.  
  88. # 创建多个线程
  89. for i in range(1):
  90. t = Thread(target=self.get_data)
  91. t_list.append(t)
  92. t.start()
  93.  
  94. # 统一回收线程
  95. for t in t_list:
  96. t.join()
  97.  
  98. # 关闭文件
  99. self.f.close()
  100. print('数量:', self.i)
  101.  
  102. if __name__ == '__main__':
  103. start = time.time()
  104. spider = XiaomiSpider()
  105. spider.main()
  106. end = time.time()
  107. print('执行时间:%.2f' % (end - start))

2.腾讯招聘数据抓取(Ajax)

确定URL地址及目标

要求与分析

  1. 通过查看网页源码,得知所需数据均为 Ajax 动态加载
  2. 通过F12抓取网络数据包,进行分析
  3. 一级页面抓取数据: 职位名称
  4. 二级页面抓取数据: 工作职责、岗位要求

一级页面json地址(pageIndex在变,timestamp未检查)

  1. https://careers.tencent.com/tencentcareer/api/post/Query?timestamp=1563912271089&countryId=&cityId=&bgIds=&productId=&categoryId=&parentCategoryId=&attrId=&keyword=&pageIndex={}&pageSize=10&language=zh-cn&area=cn

二级页面地址(postId在变,在一级页面中可拿到)

  1. https://careers.tencent.com/tencentcareer/api/post/ByPostId?timestamp=1563912374645&postId={}&language=zh-cn

useragents.py文件

  1. ua_list = [
  2. 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/14.0.835.163 Safari/535.1',
  3. 'Mozilla/5.0 (Windows NT 6.1; WOW64; rv:6.0) Gecko/20100101 Firefox/6.0',
  4. 'Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.1; WOW64; Trident/4.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0; .NET4.0C; InfoPath.3)',
  5. ]

非多线程爬取

  1. import time
  2. import json
  3. import random
  4. import requests
  5. from useragents import ua_list
  6.  
  7. class TencentSpider(object):
  8. def __init__(self):
  9. self.one_url = 'https://careers.tencent.com/tencentcareer/api/post/Query?timestamp=1563912271089&countryId=&cityId=&bgIds=&productId=&categoryId=&parentCategoryId=&attrId=&keyword=&pageIndex={}&pageSize=10&language=zh-cn&area=cn'
  10. self.two_url = 'https://careers.tencent.com/tencentcareer/api/post/ByPostId?timestamp=1563912374645&postId={}&language=zh-cn'
  11. self.f = open('tencent.json', 'a') # 打开文件
  12. self.item_list = [] # 存放抓取的item字典数据
  13.  
  14. # 获取响应内容函数
  15. def get_page(self, url):
  16. headers = {'User-Agent': random.choice(ua_list)}
  17. html = requests.get(url=url, headers=headers).text
  18. html = json.loads(html) # json格式字符串转为Python数据类型
  19.  
  20. return html
  21.  
  22. # 主线函数: 获取所有数据
  23. def parse_page(self, one_url):
  24. html = self.get_page(one_url)
  25. item = {}
  26. for job in html['Data']['Posts']:
  27. item['name'] = job['RecruitPostName'] # 名称
  28. post_id = job['PostId'] # postId,拿postid为了拼接二级页面地址
  29. # 拼接二级地址,获取职责和要求
  30. two_url = self.two_url.format(post_id)
  31. item['duty'], item['require'] = self.parse_two_page(two_url)
  32. print(item)
  33. self.item_list.append(item) # 添加到大列表中
  34.  
  35. # 解析二级页面函数
  36. def parse_two_page(self, two_url):
  37. html = self.get_page(two_url)
  38. duty = html['Data']['Responsibility'] # 工作责任
  39. duty = duty.replace('\r\n', '').replace('\n', '') # 去掉换行
  40. require = html['Data']['Requirement'] # 工作要求
  41. require = require.replace('\r\n', '').replace('\n', '') # 去掉换行
  42.  
  43. return duty, require
  44.  
  45. # 获取总页数
  46. def get_numbers(self):
  47. url = self.one_url.format(1)
  48. html = self.get_page(url)
  49. numbers = int(html['Data']['Count']) // 10 + 1 # 每页有10个推荐
  50.  
  51. return numbers
  52.  
  53. def main(self):
  54. number = self.get_numbers()
  55. for page in range(1, 3):
  56. one_url = self.one_url.format(page)
  57. self.parse_page(one_url)
  58.  
  59. # 保存到本地json文件:json.dump
  60. json.dump(self.item_list, self.f, ensure_ascii=False)
  61. self.f.close()
  62.  
  63. if __name__ == '__main__':
  64. start = time.time()
  65. spider = TencentSpider()
  66. spider.main()
  67. end = time.time()
  68. print('执行时间:%.2f' % (end - start))

多线程爬取

多线程即把所有一级页面链接提交到队列,进行多线程数据抓取

  1. import requests
  2. import json
  3. import time
  4. import random
  5. from useragents import ua_list
  6. from threading import Thread
  7. from queue import Queue
  8.  
  9. class TencentSpider(object):
  10. def __init__(self):
  11. self.one_url = 'https://careers.tencent.com/tencentcareer/api/post/Query?timestamp=1563912271089&countryId=&cityId=&bgIds=&productId=&categoryId=&parentCategoryId=&attrId=&keyword=&pageIndex={}&pageSize=10&language=zh-cn&area=cn'
  12. self.two_url = 'https://careers.tencent.com/tencentcareer/api/post/ByPostId?timestamp=1563912374645&postId={}&language=zh-cn'
  13. self.q = Queue()
  14. self.i = 0 # 计数
  15.  
  16. # 获取响应内容函数
  17. def get_page(self, url):
  18. headers = {'User-Agent': random.choice(ua_list)}
  19. html = requests.get(url=url, headers=headers).text
  20. # json.loads()把json格式的字符串转为python数据类型
  21. html = json.loads(html)
  22.  
  23. return html
  24.  
  25. # 主线函数: 获取所有数据
  26. def parse_page(self):
  27. while True:
  28. if not self.q.empty():
  29. one_url = self.q.get()
  30. html = self.get_page(one_url)
  31. item = {}
  32. for job in html['Data']['Posts']:
  33. item['name'] = job['RecruitPostName'] # 名称
  34. post_id = job['PostId'] # 拿postid为了拼接二级页面地址
  35. # 拼接二级地址,获取职责和要求
  36. two_url = self.two_url.format(post_id)
  37. item['duty'], item['require'] = self.parse_two_page(two_url)
  38. print(item)
  39. # 每爬取按完成1页随机休眠
  40. time.sleep(random.uniform(0, 1))
  41. else:
  42. break
  43.  
  44. # 解析二级页面函数
  45. def parse_two_page(self, two_url):
  46. html = self.get_page(two_url)
  47. # 用replace处理一下特殊字符
  48. duty = html['Data']['Responsibility']
  49. duty = duty.replace('\r\n', '').replace('\n', '')
  50. # 处理要求
  51. require = html['Data']['Requirement']
  52. require = require.replace('\r\n', '').replace('\n', '')
  53.  
  54. return duty, require
  55.  
  56. # 获取总页数
  57. def get_numbers(self):
  58. url = self.one_url.format(1)
  59. html = self.get_page(url)
  60. numbers = int(html['Data']['Count']) // 10 + 1
  61.  
  62. return numbers
  63.  
  64. def main(self):
  65. # one_url入队列
  66. number = self.get_numbers()
  67. for page in range(1, number + 1):
  68. one_url = self.one_url.format(page)
  69. self.q.put(one_url)
  70.  
  71. t_list = []
  72. for i in range(5):
  73. t = Thread(target=self.parse_page)
  74. t_list.append(t)
  75. t.start()
  76.  
  77. for t in t_list:
  78. t.join()
  79.  
  80. print('数量:', self.i)
  81.  
  82. if __name__ == '__main__':
  83. start = time.time()
  84. spider = TencentSpider()
  85. spider.main()
  86. end = time.time()
  87. print('执行时间:%.2f' % (end - start))

多进程实现

  1. import requests
  2. import json
  3. import time
  4. import random
  5. from useragents import ua_list
  6. from multiprocessing import Process
  7. from queue import Queue
  8.  
  9. class TencentSpider(object):
  10. def __init__(self):
  11. self.one_url = 'https://careers.tencent.com/tencentcareer/api/post/Query?timestamp=1563912271089&countryId=&cityId=&bgIds=&productId=&categoryId=&parentCategoryId=&attrId=&keyword=&pageIndex={}&pageSize=10&language=zh-cn&area=cn'
  12. self.two_url = 'https://careers.tencent.com/tencentcareer/api/post/ByPostId?timestamp=1563912374645&postId={}&language=zh-cn'
  13. self.q = Queue()
  14.  
  15. # 获取响应内容函数
  16. def get_page(self, url):
  17. headers = {'User-Agent': random.choice(ua_list)}
  18. html = requests.get(url=url, headers=headers).text
  19. # json格式字符串 -> Python
  20. html = json.loads(html)
  21.  
  22. return html
  23.  
  24. # 主线函数: 获取所有数据
  25. def parse_page(self):
  26. while True:
  27. if not self.q.empty():
  28. one_url = self.q.get()
  29. html = self.get_page(one_url)
  30. item = {}
  31. for job in html['Data']['Posts']:
  32. # 名称
  33. item['name'] = job['RecruitPostName']
  34. # postId
  35. post_id = job['PostId']
  36. # 拼接二级地址,获取职责和要求
  37. two_url = self.two_url.format(post_id)
  38. item['duty'], item['require'] = self.parse_two_page(two_url)
  39.  
  40. print(item)
  41. else:
  42. break
  43.  
  44. # 解析二级页面函数
  45. def parse_two_page(self, two_url):
  46. html = self.get_page(two_url)
  47. # 用replace处理一下特殊字符
  48. duty = html['Data']['Responsibility']
  49. duty = duty.replace('\r\n', '').replace('\n', '')
  50. # 处理要求
  51. require = html['Data']['Requirement']
  52. require = require.replace('\r\n', '').replace('\n', '')
  53.  
  54. return duty, require
  55.  
  56. # 获取总页数
  57. def get_numbers(self):
  58. url = self.one_url.format(1)
  59. html = self.get_page(url)
  60. numbers = int(html['Data']['Count']) // 10 + 1
  61.  
  62. return numbers
  63.  
  64. def main(self):
  65. # url入队列
  66. number = self.get_numbers()
  67. for page in range(1, number + 1):
  68. one_url = self.one_url.format(page)
  69. self.q.put(one_url)
  70.  
  71. t_list = []
  72. for i in range(4):
  73. t = Process(target=self.parse_page)
  74. t_list.append(t)
  75. t.start()
  76.  
  77. for t in t_list:
  78. t.join()
  79.  
  80. if __name__ == '__main__':
  81. start = time.time()
  82. spider = TencentSpider()
  83. spider.main()
  84. end = time.time()
  85. print('执行时间:%.2f' % (end - start))

基于multiprocessing.dummy线程池的数据爬取

案例:爬取梨视频数据。在爬取和持久化存储方面比较耗时,所以两个都需要多线程

  1. import requests
  2. import re
  3. from lxml import etree
  4. from multiprocessing.dummy import Pool
  5. import random
  6.  
  7. pool = Pool(5) # 实例化一个线程池对象
  8.  
  9. url = 'https://www.pearvideo.com/category_1'
  10. headers = {
  11. 'User-Agent':'Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.119 Safari/537.36'
  12. }
  13. page_text = requests.get(url=url,headers=headers).text
  14. tree = etree.HTML(page_text)
  15. li_list = tree.xpath('//div[@id="listvideoList"]/ul/li')
  16.  
  17. video_url_list = []
  18. for li in li_list:
  19. detail_url = 'https://www.pearvideo.com/'+li.xpath('./div/a/@href')[0]
  20. detail_page = requests.get(url=detail_url,headers=headers).text
  21. video_url = re.findall('srcUrl="(.*?)",vdoUrl',detail_page,re.S)[0]
  22. video_url_list.append(video_url)
  23.  
  24. # pool.map(回调函数,可迭代对象)函数依次执行对象
  25. video_data_list = pool.map(getVideoData,video_url_list) # 获取视频
  26.  
  27. pool.map(saveVideo,video_data_list) # 持久化存储
  28.  
  29. def getVideoData(url):
  30. return requests.get(url=url,headers=headers).content
  31.  
  32. def saveVideo(data):
  33. fileName = str(random.randint(0,5000))+'.mp4' # 因回调函数只能传一个参数,所以没办法再传名字了,只能自己取名
  34. with open(fileName,'wb') as fp:
  35. fp.write(data)
  36.  
  37. pool.close()
  38. pool.join()

Python爬虫进阶 | 多线程的更多相关文章

  1. Python爬虫进阶五之多线程的用法

    前言 我们之前写的爬虫都是单个线程的?这怎么够?一旦一个地方卡到不动了,那不就永远等待下去了?为此我们可以使用多线程或者多进程来处理. 首先声明一点! 多线程和多进程是不一样的!一个是 thread ...

  2. Python爬虫进阶四之PySpider的用法

    审时度势 PySpider 是一个我个人认为非常方便并且功能强大的爬虫框架,支持多线程爬取.JS动态解析,提供了可操作界面.出错重试.定时爬取等等的功能,使用非常人性化. 本篇内容通过跟我做一个好玩的 ...

  3. Python爬虫进阶一之爬虫框架概述

    综述 爬虫入门之后,我们有两条路可以走. 一个是继续深入学习,以及关于设计模式的一些知识,强化Python相关知识,自己动手造轮子,继续为自己的爬虫增加分布式,多线程等功能扩展.另一条路便是学习一些优 ...

  4. Python爬虫进阶三之Scrapy框架安装配置

    初级的爬虫我们利用urllib和urllib2库以及正则表达式就可以完成了,不过还有更加强大的工具,爬虫框架Scrapy,这安装过程也是煞费苦心哪,在此整理如下. Windows 平台: 我的系统是 ...

  5. Python爬虫进阶之Scrapy框架安装配置

    Python爬虫进阶之Scrapy框架安装配置 初级的爬虫我们利用urllib和urllib2库以及正则表达式就可以完成了,不过还有更加强大的工具,爬虫框架Scrapy,这安装过程也是煞费苦心哪,在此 ...

  6. python爬虫之多线程、多进程+代码示例

    python爬虫之多线程.多进程 使用多进程.多线程编写爬虫的代码能有效的提高爬虫爬取目标网站的效率. 一.什么是进程和线程 引用廖雪峰的官方网站关于进程和线程的讲解: 进程:对于操作系统来说,一个任 ...

  7. 芝麻软件: Python爬虫进阶之爬虫框架概述

    综述 爬虫入门之后,我们有两条路可以走. 一个是继续深入学习,以及关于设计模式的一些知识,强化Python相关知识,自己动手造轮子,继续为自己的爬虫增加分布式,多线程等功能扩展.另一条路便是学习一些优 ...

  8. Python爬虫之多线程下载豆瓣Top250电影图片

    爬虫项目介绍   本次爬虫项目将爬取豆瓣Top250电影的图片,其网址为:https://movie.douban.com/top250, 具体页面如下图所示:   本次爬虫项目将分别不使用多线程和使 ...

  9. Python爬虫之多线程下载程序类电子书

      近段时间,笔者发现一个神奇的网站:http://www.allitebooks.com/ ,该网站提供了大量免费的编程方面的电子书,是技术爱好者们的福音.其页面如下:   那么我们是否可以通过Py ...

随机推荐

  1. 回文树/回文自动机(PAM)学习笔记

    回文树(也就是回文自动机)实际上是奇偶两棵树,每一个节点代表一个本质不同的回文子串(一棵树上的串长度全部是奇数,另一棵全部是偶数),原串中每一个本质不同的回文子串都在树上出现一次且仅一次. 一个节点的 ...

  2. The underlying connection was closed: An unexpected error occurred on a rece

    服务器问题,在后台访问外网了,特别是https的网站,容易出这个问题. 修改服务器配置,或修改代码解决.

  3. .net core 使用swagger接口描述

    首先安装nuget包 Swashbuckle.AspNetCore.Swagger Swashbuckle.AspNetCore.SwaggerGen Swashbuckle.AspNetCore.S ...

  4. Hybris产品主数据的价格折扣维护

    登录Hybris backoffice的产品管理界面,进入price标签页,点击Create new Discount Row按钮: 在Discount下拉地段里选择10%的折扣,这个产品原来的单价是 ...

  5. [LeetCode] 78. 子集 ☆☆☆(回溯)

    描述 给定一组不含重复元素的整数数组 nums,返回该数组所有可能的子集(幂集). 说明:解集不能包含重复的子集. 示例: 输入: nums = [1,2,3]输出:[ [3],  [1],  [2] ...

  6. sql server快捷键添加

    工具--选项--键盘 sp_table_column_info p_helpindex sp_sql

  7. 数据库与数据仓库的区别实际讲的是OLTP与OLAP的区别

    什么是数据仓库 数据仓库,英文名称为Data Warehouse,可简写为DW或DWH.数据仓库,是为企业所有级别的决策制定过程,提供所有类型数据支持的战略集合.它出于分析性报告和决策支持目的而创建. ...

  8. echarts 如何在世界地图中绘制中国地图

    1.导入 world.china.js  这个js是将world.js 文件 以及china.js文件进行合并 (网上一些中国地图勾勒的身份曲线感觉很飘  所以自己加工了一下china.js中的数据, ...

  9. JAVA-JNI调用使用

    准备工作: 1.打开eclipse,新建c++项目,编写c++ jni接口如下图: 2.编译运行生成dll文件,导入到java项目,在java中创建调用使用,如下图: C文件定义: 头文件 /* DO ...

  10. Pthon魔术方法(Magic Methods)-实例化

    Pthon魔术方法(Magic Methods)-实例化 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 一.实例化一个对象 __new__: 该方法需要返回一个值,如果该值不是cl ...