python实现一个很简单的多线程爬虫

需求

无聊用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')

改进

  • 网络请求获取数据部分
    可以将网络请求和数据处理进行分开线程处理,一部分线程进行网络请求,一部分进行数据处理(生产者消费者模式),用Queue等线程安全的结构进行线程间通讯。本次由于处理数据并不复杂所以放在一个线程同时处理问题不大。
  • 数据保存部分
    可以先将数据封装成SQL的文件再导入数据库就不存在访问数据库线程安全问题
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值