概述
爬取网站的流程:
-
确定网站哪个url是数据的来源
-
简要分析网站结构,查看数据在哪里
-
查看是否有分页,解决分页问题
-
发送请求,查看response.text里面是否有我们想要的数据
-
如果有数据,提取,保存
-
如果没有,我们就可以通过以下两种方式来实现爬取
-
分析数据来源,查看是否可以通过一些接口获取数据(首推)
应该首先想到,数据可能是从ajax接口中获取的。
分析接口的步骤:
- 查看改接口返回的数据是否是我们想要的
- 重点查看该接口的请求参数,了解哪些请求参数的变化的,以及是怎么变化的
-
selenium+phantomjs来获取页面内容
-
格式化字符串的三种方法
- ‘……%s’%i
- ‘…{3}…{2}…{1}’.format(a,b,c)
- f’……filename’
一、程序、进程和线程
定义
程序:一个应用可以当做一个程序,比如qq。
进程:程序运行最小的资源分配单位,一个程序可以有多个进程。
线程:cpu调度的最小单位,必须依赖进程而存在。线程没有独立的资源,所有线程共享他所属进程的资源。
一个程序至少有一个进程,一个进程至少有一个线程。
二、多线程
多线程是指一个程序包含多个并行的线程来完成不同的任务。
优点:可以提高cpu的利用率。
(一)创建
1.创建多线程的第一种方法
(1)导包
import threading
(2)创建一个线程
t = threading.Thread(
target = 方法名,
args = (1,) # 方法的参数(元组类型)
)
(3)启动线程
t.start()
例:下载文件(单线程)
import time
import random
import threading
# 单线程爬虫
def download(fileName):
print(f"{fileName}文件开始下载")
time.sleep(random.random()*10)
print(f"{fileName}文件完成下载")
# 单线程 默认主线程
if __name__ == '__main__':
for i in range(5):
download(i)
例:下载文件(多线程)
import time
import random
import threading
def download(fileName):
print(f"{fileName}文件开始下载")
time.sleep(random.random()*10)
print(f"{fileName}文件完成下载")
# 多线程
if __name__ == '__main__':
for i in range(5):
t = threading.Thread(target=download,args=(i,))
t.start()
print(threading.enumerate()) # 加主线程,共6个
线程生存期
当我们启动一个线程到这个线程的任务方法执行完毕的过程,就是该线程的生存周期。
查看线程数量
threading.enumerate() # 可以查看当前进程下的运行的线程
例:多线程
import random,time,threading
def sing():
for i in range(3):
print(f'{i}正在唱歌')
time.sleep(random.random())
def dance():
for i in range(3):
print(f'{i}正在跳舞')
time.sleep(random.random())
if __name__ == '__main__':
# 创建线程来启动这两个任务
t1 = threading.Thread(target=sing)
t2 = threading.Thread(target=dance)
t1.start()
t2.start()
while True:
length = len(threading.enumerate())
print(f'当前运行的线程数量:{length}')
time.sleep(random.random())
if length <= 1:
break
2.创建多线程的第二种方法(通过线程类创建)
(1)继承threading.Thread
(2)重写run方法
(3)实例化这个类就可以创建线程,之后再调用start方法启动即可
线程类传参
必须在线程类的__init__
方法中调用父类的__init__
方法
两种方法:
# 1
super().__init__()
# 2
threading.Thread.__init__(self)
例
import threading,time
class MyThread(threading.Thread):
def __init__(self,filename):
self.filename = filename
print('线程开始启动----')
threading.Thread.__init__(self)
def run(self):
print(f'线程开始下载{self.filename}====')
if __name__ == '__main__':
t = MyThread('log.png')
t.start()
线程类中,我们实例化线程类的时候,可以通过指定name这个参数,给线程起名。
t = MyThread(name = 'download')
t.start()
在线程类中调用self.name,使用线程名称。
如果不设置名称,默认就是Thread-1,Thread-2,……
import threading
class MyThread(threading.Thread):
def run(self):
print('%s正在下载...'%self.name)
if __name__ == '__main__':
t = MyThread(name='download')
t.start()
# 如果不传则默认线程名称Thread-1,Thread-2...以此类推
for i in range(5):
t = MyThread()
t.start()
(二)执行顺序
线程的执行顺序是不固定的,主要是由线程的状态决定。
from threading import Thread
import time
class MyThread(Thread):
def __init__(self,filename):
super(MyThread, self).__init__()
self.filename = filename
def run(self):
for i in range(3):
time.sleep(1)
print(f'当前的线程是:{self.name},正在下载:{self.filename}')
if __name__ == '__main__':
for i in range(1,4):
t = MyThread(i)
t.start()
'''
当前的线程是:Thread-3,正在下载:3
当前的线程是:Thread-1,正在下载:1
当前的线程是:Thread-2,正在下载:2
当前的线程是:Thread-2,正在下载:2
当前的线程是:Thread-3,正在下载:3
当前的线程是:Thread-1,正在下载:1
当前的线程是:Thread-2,正在下载:2
当前的线程是:Thread-3,正在下载:3
当前的线程是:Thread-1,正在下载:1
'''
五种状态
- 新建:线程创建
- 就绪状态:当启动线程后,线程就进入就绪状态,就绪状态的线程会被放在一个cpu调度队列中,cpu会负责让其中的线程运行,变为运行状态
- 运行状态:cpu调度一个就绪状态的线程,该线程就变为了运行状态
- 阻塞状态:当运行状态的线程被阻塞就变为了阻塞状态,阻塞状态的线程要重新变为就绪状态才能继续执行
- 死亡:线程执行完毕
(三)问题
多个线程对公有变量处理的时候,容易造成数据的混乱,造成数据不安全的问题。
from threading import Thread
import time
import random
g_num = 100
def work1():
global g_num
for i in range(3):
g_num += 1
time.sleep(random.random())
print('in work1,gum=%d' % g_num)
def work2():
global g_num
for i in range(3):
g_num += 1
time.sleep(random.random())
print('in work2,gum=%d' % g_num)
if __name__ == '__main__':
t1 = Thread(target=work1)
t2 = Thread(target=work2)
t1.start()
t2.start()
from threading import Thread
g_num = 0
def test1():
global g_num
for i in range(1000000):
g_num += 1
print("---test1---g_num=%d"%g_num)
def test2():
global g_num
for i in range(1000000):
g_num += 1
print("---test2---g_num=%d"%g_num)
if __name__ == '__main__':
p1 = Thread(target=test1)
p2 = Thread(target=test2)
p1.start()
p2.start()
互斥锁
通过互斥锁确保线程之间数据的正确
步骤
-
创建锁对象
mutex = threading.Lock()
-
上锁,释放锁
if mutex.acquire(): # 此函数默认参数为True,填写False时,不阻塞,互斥锁就失去意义了 ''' 对公有变量的处理 ''' mutex.release() # 释放锁
使用互斥锁解决线程不安全:
import threading
g_num = 0
def w1():
global g_num
for i in range(10000000):
#上锁
mutexFlag = mutex.acquire(True)
if mutexFlag:
g_num+=1
#解锁
mutex.release()
print("test1---g_num=%d"%g_num)
def w2():
global g_num
for i in range(10000000):
# 上锁
mutexFlag = mutex.acquire(True)
if mutexFlag:
g_num+=1
# 解锁
mutex.release()
print("test2---g_num=%d" % g_num)
if __name__ == "__main__":
#创建锁
mutex = threading.Lock()
t1 = threading.Thread(target=w1)
t2 = threading.Thread(target=w2)
t1.start()
t2.start()
三、多线程和多进程
(一)多线程
1.优点
程序逻辑和控制方式复杂。
所有线程可以直接共享内存和变量。
多线程消耗的总资源比多进程要少。
2.缺点
每个线程和主程序共用地址空间,受限于2GB的地址空间。
线程之间的同步和加锁控制比较麻烦。
一个线程的崩溃可能影响到整个程序的稳定性。
(二)多进程
1.优点
每个进程互相独立,子进程崩溃没关系,不影响主程序的稳定性。
增加cpu,容易扩充性能。
每个子进程都有2GB地址空间和相关资源,总体能够达到的性能上限非常大。
2.缺点
逻辑控制复杂,需要和主程序交互。
需要跨进进程边界,不适合大数据量传送,适合小数据量传送、密集运算。
多进程调度开销比较大。
在实际开发中,选择多线程和多进程应该通过具体实际开发情况进行选择。最好是多进程和多线程结合,即根据实际的需要,每个cpu开辟一个子进程,每个子进程开启多个线程,可以对若干同类型的数据进行处理。
四、死锁
原因
产生死锁的情况有两种:
- 当一个线程获取了锁之后,还未释放锁的前提下,试图获取另一把锁,此时会产生死锁
- 线程A获取锁1,线程B获取锁2,线程A还未释放锁1,想要继续获取锁2,线程B还未释放锁2,同时想要获取锁1
五、项目
腾讯招聘(ajax)
import requests,json,time
class Tencent(object):
def __init__(self,url):
self.url = url
self.parse()
def write_to_file(self,list_):
for item in list_:
with open('tencent_infos.txt','a',encoding='utf-8') as fp:
fp.write(str(item)+'n')
def parse_json(self,text):
infos = []
dict = json.loads(text)
for data in dict['Data']['Posts']:
item = {}
# 职位名
item['RecruitPostName'] = data['RecruitPostName']
# 职位类型
item['CategoryName'] = data['CategoryName']
# 职责
item['Responsibility'] = data['Responsibility']
# 发布时间
item['LastUpdateTime'] = data['LastUpdateTime']
# 详情页链接
item['PostURL'] = data['PostURL']
infos.append(item)
self.write_to_file(infos)
def parse(self):
for i in range(1,51):
response = requests.get(self.url %i)
self.parse_json(response.text)
if __name__ == '__main__':
start = time.time()
base_url = 'https://careers.tencent.com/tencentcareer/api/post/Query?timestamp=1572856544479&countryId=&cityId=&bgIds=&productId=&categoryId=&parentCategoryId=&attrId=&keyword=&pageIndex=1&pageSize=%s&language=zh-cn&area=cn'
Tencent(base_url)
print(time.time()-start) # 17.27698802947998
多线程(一)
每页都使用了一个线程,速度很快,但会浪费资源
import requests,json,time,threading
class Tencent(object):
def __init__(self,url):
self.url = url
def write_to_file(self,list_):
for item in list_:
with open('tencent_infos.txt','a',encoding='utf-8') as fp:
fp.write(str(item)+'n')
def parse_json(self,text):
infos = []
dict = json.loads(text)
for data in dict['Data']['Posts']:
item = {}
# 职位名
item['RecruitPostName'] = data['RecruitPostName']
# 职位类型
item['CategoryName'] = data['CategoryName']
# 职责
item['Responsibility'] = data['Responsibility']
# 发布时间
item['LastUpdateTime'] = data['LastUpdateTime']
# 详情页链接
item['PostURL'] = data['PostURL']
infos.append(item)
self.write_to_file(infos)
def parse(self):
response = requests.get(self.url)
self.parse_json(response.text)
if __name__ == '__main__':
start = time.time()
base_url = 'https://careers.tencent.com/tencentcareer/api/post/Query?timestamp=1572856544479&countryId=&cityId=&bgIds=&productId=&categoryId=&parentCategoryId=&attrId=&keyword=&pageIndex=1&pageSize=%s&language=zh-cn&area=cn'
crawl_list = []
for i in range(1, 51):
tencent = Tencent(base_url % i)
# 用第一种方法开启线程
t = threading.Thread(target=tencent.parse)
t.start()
crawl_list.append(t)
# 将每个线程都调用join方法,保证所得的运行时间是在所有线程完毕之后的时间
for t in crawl_list:
t.join()
print(time.time()-start) # 1.9531118869781494
多线程(二)
使用消息队列
import requests,json,time,threading
from queue import Queue
class Tencent(threading.Thread):
def __init__(self,url,name,q):
super().__init__()
self.url = url
self.q = q
self.name = name
def run(self):
self.parse()
def write_to_file(self,list_):
for item in list_:
with open('tencent_infos.txt','a',encoding='utf-8') as fp:
fp.write(str(item)+'n')
def parse_json(self,text):
infos = []
dict = json.loads(text)
for data in dict['Data']['Posts']:
item = {}
# 职位名
item['RecruitPostName'] = data['RecruitPostName']
# 职位类型
item['CategoryName'] = data['CategoryName']
# 职责
item['Responsibility'] = data['Responsibility']
# 发布时间
item['LastUpdateTime'] = data['LastUpdateTime']
# 详情页链接
item['PostURL'] = data['PostURL']
infos.append(item)
self.write_to_file(infos)
def parse(self):
while True:
if self.q.empty():
break
page = self.q.get()
print(f'======第{page}页======in{self.name}')
response = requests.get(self.url%page)
self.parse_json(response.text)
if __name__ == '__main__':
start = time.time()
base_url = 'https://careers.tencent.com/tencentcareer/api/post/Query?timestamp=1572856544479&countryId=&cityId=&bgIds=&productId=&categoryId=&parentCategoryId=&attrId=&keyword=&pageIndex=1&pageSize=%s&language=zh-cn&area=cn'
# 1.创建任务队列
q = Queue()
# 2.给队列添加任务,任务是每一页的页码
for page in range(1,51):
q.put(page)
# print(q) # <queue.Queue object at 0x000000000395F668>
# while not q.empty():
# print(q.get())
crawl_list = ['aa','bb','cc','dd','ee']
list_ = []
for name in crawl_list:
t = Tencent(base_url,name,q)
t.start()
list_.append(t)
for l in list_:
l.join()
print(time.time()-start) # 3.4191956520080566
最后
以上就是大意黑米为你收集整理的爬虫(七)--程序,多进程,多线程一、程序、进程和线程二、多线程三、多线程和多进程四、死锁五、项目的全部内容,希望文章能够帮你解决爬虫(七)--程序,多进程,多线程一、程序、进程和线程二、多线程三、多线程和多进程四、死锁五、项目所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复