网络爬虫学习心得2(多线程爬取小说)

有了爬虫的基础知识,接下来爬取笔趣阁网站小说作为练手。《剑来》是我比较喜欢的一本小说,很值得一看。目前小说还没完结,笔趣阁只更新了603章,因此就凑整数爬取前600章节。

网站结构的分析
从笔趣阁目录页可以看到,要爬取每一个章节的内容需要获取每一个章节的地址。每一个章节的地址都在“http://www.biquduge.com/12_12785/”这个目录下后面再添加后续7位数字的页码,这些页码没有一定的规律,因此爬取每一章节的页码,写入文件。这里为什么要写入文件,是为了后续用多线程进行爬虫。

#爬取http://www.biquduge.com/12_12785/首页中每一章节的地址
r = requests.get('http:/www.biquduge.com/12_12785/',headers=header,timeout=10)
soup = BeautifulSoup(r.text,'lxml')
lis = soup.find('div',id='list')
content = lis.find_all('a')
for item in content:
    if item.string == None:
        continue
    else:
        with open(r'd:\ip.text','a+',encoding='utf-8') as f:
            f.write(item.get("href")+'\n')
with open(r'd:\ip.text','r') as f:
    for i in range(600):
        line = f.readline().strip('\n')
        link_add.append(link+line)
        i+=1
    f.close()

爬取小说内容
小说内容分为章节名称和每一章节的正文,有之前学习的基础也比较容易可以完成简单爬虫代码。

for i in range(600):
    r = requests.get(link_add[i],headers=header,timeout=30)
    r.encoding = r.apparent_encoding
    soup = BeautifulSoup(r.text,'lxml')
    chapter = soup.find('div',class_='bookname').find('h1')
    article = soup.find('div',id='content')
    with open(r'd:\jianlai.text','a+',encoding='utf-8') as f:
        f.write(chapter.text.strip()+'\n')
        f.write(article.text.strip()+'\n')
    i +=1

在爬取过程中,会存在中文乱码的问题。网上查找了一些解决的方法,发现一个很简单的解决方法,在requests获取网页的编码格式时会从HTTP header头里猜测响应的编码方式,但这个编码方式和响应内容的编码方式不同,因此可以用apparent_encoding来指定。

r.encoding = r.apparent_encoding

解决了乱码的问题后,能正常爬取到整本小说,再写入到文本文件。
完成小说爬取的整体代码如下:

import time
import requests
from bs4 import BeautifulSoup

start = time.time()
link = 'http://www.biquduge.com'
header = {'user-agent':'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.108 Safari/537.36'}
link_add = []

#爬取http://www.biquduge.com/12_12785/首页中每一章节的地址
r = requests.get('http:/www.biquduge.com/12_12785/',headers=header,timeout=10)
soup = BeautifulSoup(r.text,'lxml')
lis = soup.find('div',id='list')
content = lis.find_all('a')
for item in content:
    if item.string == None:
        continue
    else:
        with open(r'd:\ip.text','a+',encoding='utf-8') as f:
            f.write(item.get("href")+'\n')

with open(r'd:\ip.text','r') as f:
    for i in range(600):
        line = f.readline().strip('\n')
        link_add.append(link+line)
        i+=1
    f.close()

for i in range(600):
    r = requests.get(link_add[i],headers=header,timeout=30)
    r.encoding = r.apparent_encoding
    soup = BeautifulSoup(r.text,'lxml')
    chapter = soup.find('div',class_='bookname').find('h1')
    article = soup.find('div',id='content')
    with open(r'd:\jianlai2.txt','a+',encoding='utf-8') as f:
        f.write(chapter.text.strip()+'\n'+'\n')
        f.write(article.text.strip()+'\n'+'\n'+'\n')
    i +=1

end = time.time()
print(end-start)

整体耗费的时间为:1201.2683999538422秒

多线程爬虫
在这种网页爬虫由于是单线程,在爬取过程中有很多IO操作并要等待IO操作,造成不必要的时间浪费。因此利用多线程,可以避免CPU资源浪费的情况下,减少IO等待的时间,缩短爬虫的时间。多线程是非常适合网络爬虫这种IO密集型的工作。
多线程在python3可以用threading库的thread,重写thread的__init__()和run()方法。主要包括以下的方法:
run():用以表示线程活动的方法。
start():启动线程活动。
join():等待至线程中止。阻塞调用线程直至线程的join()方法被调用为止。

在前面《剑来》小说爬取的代码基础上,进行多线程的修改。

import time
import requests
from bs4 import BeautifulSoup
import threading
import queue as Queue

start = time.time()
link = 'http://www.biquduge.com'
header = {'user-agent':'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.108 Safari/537.36'}
link_add = []

#爬取http://www.biquduge.com/12_12785/首页中每一章节的地址
r = requests.get('http:/www.biquduge.com/12_12785/',headers=header,timeout=10)
soup = BeautifulSoup(r.text,'lxml')
lis = soup.find('div',id='list')
content = lis.find_all('a')
for item in content:
    if item.string == None:
        continue
    else:
        with open(r'd:\ip.text','a+',encoding='utf-8') as f:
            f.write(item.get("href")+'\n')

with open(r'd:\ip.text','r') as f:
    for i in range(600):
        line = f.readline().strip('\n')
        link_add.append(link+line)
        i+=1
    f.close()

class mythread(threading.Thread):
    def __init__(self,name,q):
        threading.Thread.__init__(self)
        self.name = name
        self.q = q
    def run(self):
        while True:
            try:
                crawler(self.name,self.q)
            except:
                break

def crawler(threadName,q):
    url = q.get(timeout=5)
    try:
        r = requests.get(url,headers=header,timeout=30)
        r.encoding = r.apparent_encoding
        soup = BeautifulSoup(r.text,'lxml')
        chapter = soup.find('div',class_='bookname').find('h1')
        article = soup.find('div',id='content')
        with open(r'd:\jianlai.text','a+',encoding='utf-8') as f:
            f.write(chapter.text.strip()+'\n')
            f.write(article.text.strip()+'\n'+'\n'+'\n')
      
    except Exception as e:
        print(q.qsize(),threadName,url,'Error:',e)      

threadList = ['thread-1','thread-2','thread-3','thread-4','thread-5']
workQueue = Queue.Queue(600)
threads = []
for tName in threadList:
    thread = mythread(tName,workQueue)
    thread.start()
    threads.append(thread)
    
for url in link_add:
    workQueue.put(url)
for t in threads:
    t.join()
    
end = time.time()
print(end-start)    

利用多线程完成小说爬取,开启5个线程整体耗费时间580.932021856308秒。对比单线程1201.2683999538422,缩短了50%以上的时间。

你可能感兴趣的:(网络爬虫学习心得2(多线程爬取小说))