查看线程数量——enumerate()
通常用index代表索引
enumerate()在threading线程库的作用——threading.enumerate()
加入time.sleep()观察
import threading
# threading.enumerate()
def demo1():
for i in range(5):
print('我是demo1---%s' % i)
time.sleep(1)
def demo2():
for i in range(8):
print('我是demo2---%s' % i)
time.sleep(1)
def main():
t1 =threading.Thread(target=demo1,name='demo1')
t2 = threading.Thread(target=demo2,name='demo2')
t1.start()
t2.start()
while True:
print(threading.enumerate())
if len(threading.enumerate())<=1:
break
time.sleep(1)
if __name__ == '__main__':
main()
start()的作用——开启线程创建线程
global——修改值
函数与方法
线程资源竞争
线程间共享全局变量
import threading
num = 100
def demo1():
global num
num += 1
print('demo1-num-%d' % num)
def demo2():
# global num
# num +=1
print('demo2-num-%d' % num)
def main():
t1 = threading.Thread(target=demo1, name='demo1')
t2 = threading.Thread(target=demo2, name='demo2')
t1.start() # 开起线程和创建执行线程
t2.start()
# 线程间共享全局变量
print('main-num-%d' % num)
if __name__ == '__main__':
main()
当数量变大
import threading
num = 0
# num外部是0,demo1先加1,加到最后是100
def demo1(nums):
global num
for i in range(nums): #nums接受args(100,)这个赋值100,就是循环100次
num += 1
print('demo1-num-%d' % num)
# demo2接受demo1的num值,最后结果应该是200
def demo2(nums):
global num
for i in range(nums):
num += 1
print('demo2-num-%d' % num)
def main():
t1 = threading.Thread(target=demo1, args=(100,))
t2 = threading.Thread(target=demo2, args=(100,))
t1.start() # 开起线程和创建执行线程
t2.start()
# 线程间共享全局变量
# 最后结果应该同demo2一致为200
print('main-num-%d' % num)
if __name__ == '__main__':
main()
当数量变得更大时——200w时
线程间相互争抢资源,有的时候线程1对num进行运算还没赋值,线程2也对没有重新复制的num赋值,导致结果不同
解决方法:time.sleep()
解决方法2:join()——主线程一直等待全部的子线程结束之后,主线程自身才结束,程序退出
线程锁
互斥锁
死锁
两把锁——会程序堵塞(即使解锁两次也会堵塞)
threading.RLock()可以上多次锁
queue线程
判断队列是否为空——Queue().empty()——True为空,False为非空
判断是否队列满—— Queue().full()——False 为不满,True为满
put()将一个数据添加到队列中
注意——当指定大小后,在队列已满的情况再添加数据会导致程序堵塞,解决方式1为添加参数——timeout
解决方法2:putnowait()
取数据 get() 一次取一个
一次取一个,取完再取会堵塞,解决方式也是timeout()或者getnowait()
用多线程爬取
发现应用程序是ajax动态加载,可以用selenuim或者分析页面来源,可以在network找XHR或者在ALL找
打开第一个url
普通(非多线程下的爬虫案例+csv保存)
# 发现应用程序是ajax动态加载,可以用selenuim或者分析页面来源
# 可以再network找XHR或者在ALL找
# 点开一个,url https://app.mi.com/details?id=com.kuaiyin.live
# XHR 里的packageName是这个 com.kuaiyin.live
# 所以url为‘https://app.mi.com/details?id=’ 拼接+ packageName
# 通过 XHR找到的url为
# 第一页 https://app.mi.com/categotyAllListApi?page=0
# 第二页 https://app.mi.com/categotyAllListApi?page=1
import requests
import time
import random
from fake_useragent import UserAgent
import csv
class XiaomiSpider():
def __init__(self):
# {}为占位 在后面的来一个format占位页数
self.url ='https://app.mi.com/categotyAllListApi?page={}&categoryId=2&pageSize=30'
# headers
# self.headers ={
# 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.93 Safari/537.36'
# }
# 获取响应内容 获得的内容使json数据
def get_html(self,url):
lst =[]
# 写一个假的headers
headers ={'User-Agent':UserAgent().random}
# 之前的方法 把json变成python字典类型
# response =requests.get(html) response.text
# data =json.loads(requests.text)
# 方法2
# data =requests.json()
# html =requests.get(url,headers=self.headers).json()
html = requests.get(url, headers=headers).json()
# 调用parse_html函数
lst += self.parse_html(html)
# print(item,type(item))
return lst
# 解析响应内容
def parse_html(self,html):
lst =[]
# 现在是字典类型了
# item ={}
for app_dict in html['data']:
item = {}
# 将每个html(app_dict)的应用名赋值给空字典item,并设下key和对应value值
item['app_name']=app_dict['displayName']
item['app_category'] = app_dict['level1CategoryName']
# 这个是应用程序的url
item['app_link'] = 'https://app.mi.com/details?id='+app_dict['packageName']
lst.append(item)
return lst
def save_html(self,lst):
with open('xiaomi.csv','w',encoding='utf-8',newline='')as f:
headers = ('app_name', 'app_category', 'app_link')
dwriter = csv.DictWriter(f, headers)
dwriter.writeheader()
dwriter.writerows(lst)
def main(self):
# 一共67页 因为时0为第一页,67页为66,但是range最后一个取不到,要加1 所以开始range(67)
lst =[]
for page in range(67):
url =self.url.format(page)
# 调用get_html函数
lst += self.get_html(url=url)
# self.save_html(item)
# 控制抓取频率
# n =random.randint(1,2)等于是1=<n<=2
# time.sleep(random.randint(1,2))
self.save_html(lst)
if __name__ == '__main__':
xiaomi =XiaomiSpider()
xiaomi.main()
结果
用多线程方式爬取同一个网站——速度更快
import requests
import time
import random
from fake_useragent import UserAgent
import csv
import threading
from queue import Queue
class XiaomiSpider():
def __init__(self):
# {}为占位 在后面的来一个format占位页数
self.url = 'https://app.mi.com/categotyAllListApi?page={}&categoryId=2&pageSize=30'
# 创建队列
self.q = Queue()
self.lock = threading.Lock()
def url_in(self):
# 将目标url放进队列
for page in range(67):
url = self.url.format(page)
# 将网页数据添加到队列
self.q.put(url)
# 线程事件函数 获取url 请求解析处理
def parse_html(self):
# 线程中不可能只让线程抓取一页,所以来个死循环while true,不停抓取
lst = []
while True:
# 上锁
self.lock.acquire()
if not self.q.empty():
# 获取 url
url = self.q.get()
# 解锁
self.lock.release()
headers = {'User-Agent': UserAgent().random}
# 将json类型转化为字典类型
html =requests.get(url,headers=headers).json()
# item = {}
# lst =[]
for app_dict in html['data']:
item = {}
# 将每个html(app_dict)的应用名赋值给空字典item,并设下key和对应value值
item['app_name'] = app_dict['displayName']
item['app_category'] = app_dict['level1CategoryName']
# 这个是应用程序的url
item['app_link'] = 'https://app.mi.com/details?id=' + app_dict['packageName']
lst.append(item)
# print(lst)
self.save(lst)
else:
self.lock.release()
break
def save(self,lst):
with open('xiaomi1.csv','w',encoding='utf-8',newline='')as f:
headers =('app_name','app_category','app_link')
dwriter =csv.DictWriter(f,headers)
dwriter.writeheader()
dwriter.writerows(lst)
def run(self):
# 调用方法,添加数据到队列,然后放在线程
self.url_in()
t_lst =[]
for i in range(2):
# 循环两次,创建两个线程
t =threading.Thread(target=self.parse_html)
# 这个写法跟创建t1 t2 一样
t_lst.append(t)
t.start()
if __name__ == '__main__':
spider =XiaomiSpider()
spider.run()
# JSONDecodeError 抓取频率太快了,json数据页面没加载完美及时返回