有了爬虫的基础知识,接下来爬取笔趣阁网站小说作为练手。《剑来》是我比较喜欢的一本小说,很值得一看。目前小说还没完结,笔趣阁只更新了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%以上的时间。