无聊用python写一个爬虫爬取某个视频网站的内容,获取名称地址时长和更新时间保存到数据库头,以前没用过也没学多线程所以接直接顺着来,按照python的简洁性几行代码就搞定,一刷下来没问题,保存到数据库好好的,可以六百多页视频,差不多要半个小时才搞定,多可以打一把王者了,看控制台发现就是卡在每页访问比较慢要等请求响应的过程。这就是多线程编程的典型问题等待IO过程比较慢先做其他事。
因为是每页抓取的速度比较慢导致整体慢的,所以用多线程进行请求,在等待一个线程响应的过程中另外再多次发起请求,每个线程对一个页面进行请求和处理保存。所以涉及到防止重复获取相同页面的问题,所以需要一个多线程共享的页面参数。共享变量的访问和修改进行加锁保证每次只有一个线程访问修改
#加锁
lock.acquire()
global page
if page >= 666:
lock.release()
break
url = 'https://xxxx.com/html/category/video/video2/page_'+str(page)+'.html'
page = page+1
lock.release()
使用threading.Thread()方式进行函数式开启多线程,开启线程的数量根据个人电脑情况而定(网速和C电脑性能),并且使用线程的join()函数进行堵塞等待所有线程结束主线程才结束方便计时
threads = []
for i in range(0,60):
t = threading.Thread(target=get_message, args=( ))
threads.append(t)
t.start()
for t in threads:
t.join()
本次实践用的是MySQL数据库,首先通过多线程共享一个连接时报错,网上找pymysqll的execute有独占锁机制啥的,反正就是数据库连接不是线程安全的不可以多线程公用一个数据库连接,如果多线程访问会发生冲突问题,可以每个线程单独用一个连接,或者在访问数据库时进行加锁,显然每个线程独用一个连接更加高效。考虑到数据库连接的创建和销毁花销比较大,所以追求更加高效的方法——连接池。基于面向模块编程的python,我知道我想到的别人应该已经写出来了,网上一找通过别人实践证明DBUtils最好用效率最高。直接搜索用法就可以了。
#安装DBUtils包
#pip install DBUtils
from DBUtils.PooledDB import PooledDB
pool = PooledDB(pymysql,4,host='localhost',user='root',passwd='123456',db='key_count') #4为连接池里的最少连接数
def test(project_name):
#取一个连接
global pool
db = pool.connection()
cursor = db.cursor()
#保存到数据库
try :
cursor.execute(sql)
db.commit()
except :
db.rollback()
cursor.close()
#放回连接池
db.close()
其中第二个参数是连接池里最少有4个连接的意思。其他用默认就好了
多线程爬虫是实践多线程学习的经典方式,能够很快就体验到多线程的好处。本次实践本来需要一把王者的时间才能爬取完成,开启多线程后都不到一首歌的时间。
import urllib.request
import datetime
import time
import re
import pymysql
import threading
from bs4 import BeautifulSoup
from http import cookiejar
from urllib.error import URLError
from DBUtils.PooledDB import PooledDB
def get_message():
while True:
#加锁
lock.acquire()
global page
if page >= 666:
lock.release()
break
url = 'https://xxxx.com/html/category/video/video2/page_'+str(page)+'.html'
page = page+1
lock.release()
#创建cookie会话
cookie = cookiejar.CookieJar()
headler = urllib.request.HTTPCookieProcessor(cookie)
opener = urllib.request.build_opener(headler)
#发起请求并获取相关信息
response = opener.open(urllib.request.Request(url))
h = BeautifulSoup(response, 'html.parser')
for i in range(len(h.findAll('h3'))):
name = h.findAll('h3')[i].a.getText().replace(' ','')
print(name,type(name),len(h.findAll('h3')))
address = h.findAll('h3')[i].a['href']
print(address,type(address))
update_time = re.sub(r'号|日','',h.findAll('span',{'class':'s_j'})[i].getText().replace('年','-').replace('月','-'))
print(update_time,type(update_time))
length = h.findAll('span',{'class':'z_s'})[i].getText().split(':')[1].split('\'')[0]
print(length,type(length))
sql = f"insert into resources2 (\
name,address,length,update_time\
)values(\
{name!r},{address!r},{length},{update_time!r}\
)"
print(sql)
#连接数据库
global pool
db = pool.connection()
cursor = db.cursor()
#保存到数据库
try :
cursor.execute(sql)
db.commit()
except :
db.rollback()
cursor.close()
#释放回连接池
db.close()
if __name__ == '__main__':
page = 1
lock = threading.Lock()
start = time.time()
threads = []
pool = PooledDB(pymysql,4,host='localhost',user='root',passwd='123456',db='key_count')
for i in range(0,60):
t = threading.Thread(target=get_message, args=( ))
threads.append(t)
t.start()
for t in threads:
t.join()
print('运行了:',int(time.time()-start),'s')