Bootstrap

python之单线程和多线程访问网站

——众所周知,在python爬虫中我们经常因为爬虫的速度慢而烦恼。于是就在学习中学习了一下多线程访问网站。在了解多线程的时候我们需要先了解单线程问题。我对单线程做一简单的分析。


1、单线程问题
——单线程就是我们最原始的方法,直接写出访问网站的脚本不需要任何多线程处理例如我们要访问300个网站,网站下载链接点击这里进入下载,我们现在应该已经下载到了我们所需要的300个URL文件。
下来看一下这段单线程访问网站的代码:

import requests
import time

link_list = []
with open('url.txt','r') as file:
    file_list = file.readlines()
    for eachone in file_list:
        link = eachone.split('\t')[1]
        link = link.replace('\n','')
        link_list.append(link)
    start = time.time()
    for eachone in link_list:
        try:
            r = requests.get(eachone)
            print(r.status_code,eachone)
        except Exception as e:
            print('Error',e)
    end = time.time()
    print('串行的总时间为:',end-start)

——从这段代码中我们可以清晰的看到这就仅仅是一个单线程访问的代码,我们可以看到它的运行是时间比较长。
这是单线程运行的时间


2、简单的多线程爬虫

——多线程问题,首先做的是我们要利用threading模块提供的Thread类方法来处理线程,里面有以下几个方法:

  • run():用以表现线程活动的方法;
  • start():启动线程活动;
  • join([time]):等待至线程中止,阻塞调用线程直至线程的join()方法被调用为止;
  • isAlive():返回线程是否是活动的;
  • getName():返回线程名;
  • setName():设置线程名。

写多线程问题代码的基本步骤:
1)创建新的线程;2)开启新线程;3)添加线程带线程列表里面;4)等待所有线程完成。

——在完成这个访问网站的爬虫中呢,由于网站比较多,需要开启多个线程对,这300个网站进行一个平均分配,这样才能较快的完成爬虫。
下面我们看一下代码:
代码首先我们需要定义一个公共的函数:

def link_list():
    link_list = []
    with open('url.txt','r') as file:
        file_list = file.readlines()
        for eachone in file_list:
            link = eachone.split('\t')[1]
            link = link.replace('\n','')
            link_list.append(link)
    return link_list

这个多线程的代码:

import requests
import threading
import time

link_list = link_list()
start = time.time()
class myThread(threading.Thread):
    def __init__(self,name,link_range):
        threading.Thread.__init__(self)
        self.name = name
        self.link_range = link_range
    def run(self):
        print("Starting " + self.name)
        crawler(self.name,self.link_range)
        print("Exiting " + self.name)

def crawler(threadName,link_range):
    for i in range(link_range[0],link_range[1]+1):
        try:
            r = requests.get(link_list[i],timeout = 20)
            print(threadName,r.status_code,link_list[i])
        except Exception as e:
            print(threadName,"Error: ",e)

thread_list = []
#这就是对300个链接进行平均分配
link_rangs_list = [(0,60),(61,120),(121,180),(181,240),(241,300)]
#创建新线程
for i in range(1,6):
    thread = myThread("Thread-" + str(i),link_rangs_list[i-1])
    thread.start()
    thread_list.append(thread)
#等待所有线程完成
for thread in thread_list:
    thread.join()

end = time.time()
print('简单多线程爬虫的总时间为:',end - start)
print("Exiting Main Thread")

简单的多线程运行的时间很明显就能看到它的运行时间有了明显的缩减,如下图运行结果所示:
简单的多线程运行结果


3、使用Queue的多线程爬虫
——我们在使用多线程爬取时会发现一个这样的问题,当线程运行完之后开始结束的时候,会变成单线程问题。所以接下来我们会利用Queue模块中提供的同步的线程安全的队列类(包括FIFO(First In First Out)队列Queue、LIFO(Last In First)队列LifoQueue和优先级队列PriorityQueue),进行处理这些URL,每个线程都是自动获取链接,直接完成所有的网页抓取活动。
看一下代码:

import requests
import threading
import time
import queue as Queue

link_list = link_list()
start = time.time()
class myThread(threading.Thread):
    def __init__(self,name,q):
        threading.Thread.__init__(self)
        self.name = name
        self.q = q
    def run(self):
        print("Starting " + self.name)
        while True:
            try:
                crawler(self.name,self.q)
            except:
                break
        print("Exiting " + self.name)

def crawler(threadName,q):
    url = q.get(timeout=2)
    try:
        r = requests.get(url,timeout = 20)
        print(q.qsize(),threadName,r.status_code,url)
    except Exception as e:
        print(q.qsize(),threadName,"Error: ",e)

threadList = ["Thread-1","Thread-2","Thread-3","Thread-4","Thread-5"]
workQueue = Queue.Queue(300)
threads = []
#创建新线程
for tName in threadList:
    thread = myThread(tName,workQueue)
    thread.start()
    threads.append(thread)
#填充队列
for url in link_list:
    workQueue.put(url)
#等待所有线程完成
for t in threads:
    t.join()
end = time.time()
print('Queue多线程爬虫总时间为:',end-start)
print('Exiting Main Thread')

这个的运行时间相对于多线程爬虫相对来说能快一点点。多线程主要是对所有的URL进行了一个平均分配,有些窗口处理的速度快,所以先处理完了,其他窗口还没有处理完,而没处理完的窗口的资源不能够到处理完的窗口上去,这样会造成资源的闲置。Queue方法相对于是前面这种方法来说所开启的线程那个有位置就给那个线程分配处理资源,所以相对来说就能快一点。

代码下载链接:点击这里

;