网站分析
本次爬取所用的网站我选取的是情书网。
本网站的情书种类多,而且写得很富有文采,网站选取之后要做的就是分析网站。看到这个网站可以知道导航栏的数据包含了情书网的所有类别的情书,所以我们要获取的就是导航栏中的所有类别的链接,从而可以获得所有类别的里的情书。现在随便进入一个情书类别的链接,看看具体的结构。
如图我们可以知道这便就是情书内容链接入口了,最后我们所要做的就是获取这个链接进入到每一篇情书,随即获取具体的情书内容。
数据的爬取思路
我们爬取要先获取每个类别的链接,从而循环遍历链接,再爬取每个种类里面的具体情书。根据以上对情书网站的分析我们可以清楚的得到每一篇的情书的内容并不是以文件的形式嵌入网页,而是以文字的形式直接嵌入网页的。所以在爬取的过程中我们最后获取到的情书文章里肯定会有一些其他的字符,最后还要将这些字符过滤掉。
情书爬虫的编写
情书类别链接及类别名的获取
通过刚才的分析,需要先获取每个类别的链接方便后面爬取每一篇情书。这里爬取每个类别名的目的是方便后面将情书以类别名作为目录名进行保存。先定义一个爬虫类,用以爬取分析情书:
class python:
'''
爬虫类:
负责爬取相应的信息
'''
def __init__(self):
self.url_frist = 'http://www.qingshu.so'
现在定义爬取类别的方法:
def page(self):
'''
爬取每个类别的链接和类别名
:return:
'''
data1 = requests.get(self.url_frist)
data1.encoding = 'utf-8'
soup = BeautifulSoup(data1.text, 'lxml')
reponse = soup.find_all(name='ul', attrs={'class': 'menulist'})
url_list = re.findall('<li><a href="(.*?)">.*?</a></li>', str(reponse))
title_list = re.findall('<li><a href=".*?">(.*?)</a></li>', str(reponse))
return url_list, title_list
爬取每篇情书
进入每个具体的情书类别的网页后,要做的就是获得每一篇情书所在的网页,并获取所有的情书。在此之前需要处理如下几个问题:
每个类别的情书翻页爬取
从图中可以看出,每种类别的情书一般都具有超过一页,所以我们要获得每一页的链接从而获取每页的情书内容。
这里我们可以通过字符串拼接链接从而获取每一页的具体链接。但另一个问题也冒出来了,
如图所示,每种类别的页数都不一样,如何才能准确的获取到每种类别的每页情书呢?
这里我检查过所有类别,发现最多页数的类别是十页
所以我就遍历1~~10,获取1至10页的所有连接。代码如下:
for k in range(1, 10):
if self.page()[0][self.page()[1].index(class_name)][0:-5] != '':
url2 = self.url_frist + self.page()[0][self.page()[1].index(class_name)][0:-5] + '_' + str(k) + '.html'
但是那些不足十页的,程序运行到这里就报错,无法继续。这里我采用的方法是加一个判断方法,判断每一个页面的状态码,如果状态码等于200说明此类别有这一页的情书内容,否则说明没有达到这一页。具体代码如下:
url2 = self.url_frist + self.page()[0][self.page()[1].index(class_name)][0:-5] + '_' + str(k) + '.html'
data2 = requests.get(url2)
data2.encoding = 'utf-8'
if data2.status_code == 200:
print(url2)
获取每篇情书的链接
由上解决了翻页爬取的难题后需要做的就是获取每一篇情书的链接。这里操作没有什么难题,直接一个正则表达式就能准确提取到每篇情书的链接,代码如下:
if data2.status_code == 200:
print(url2)
soup1 = BeautifulSoup(data2.text, 'lxml')
url_list_text = re.compile('<a href="(.*?)".*?>阅读详细»</a></div>').findall(str(soup1))
每篇情书具体内容的爬取
得到每一篇情书的链接之后,剩下的就是获取每篇情书的具体内容,同时还要获取每篇情书的标题,方便存储到本地。
这里获取每篇情书的具体内容的方法都是一样的因为这里html语法都是一样的,方便获取。不过标题的html语法却不一样,这里我也是查找了很久才发现的问题。
如图,“爱情“之前类别标题的html语法和”爱情“之后类别的html语法不一样,
这是“爱情”之后的语法格式:
这是“爱情”之前的语法格式:
这里,我一开始爬取的时候不知道这个问题,导致我一开始出现的问题是“爱情”之后的文章都存储不了,文件夹为空。
针对这个问题,我采用的方法是分别编写两个不同的正则表达式用于提取不同的标题:
if self.page()[1].index(class_name)<11:
text_title = re.compile('<a href=".*?">(.*?)</a></h1>').findall(str(text_reponse))
else:
text_title = re.compile('<h1 class="a_title">(.*?)</h1>').findall(str(text_reponse))
现在就是对每篇情书的具体内容的具体爬取了。由于情书是以文字的形式嵌入到html文档中的,涉及的内容较多,所以我就用bs4解析的方法获取内容,而不是用正则表达式获取。具体的代码如下:
for url in url_list_text:
url3 = ''.join([self.url_frist, url])
text = requests.get(url3)
text.encoding = 'utf-8'
text_soup = BeautifulSoup(text.text, 'lxml')
text_reponse = text_soup.find_all(name='div', attrs={'id': 'artmain'})
text_reponse1 = text_soup.find_all(name='div', attrs={'class': 'a_content clearfix'})
文章的过滤
由于情书的具体文章是用bs4解析的,所以每篇情书的具体内容都包含着html的字符,如图所示:
很显然这些字符是我们所不需要的,所以需要将其过滤掉。这里我编写一个方法用正则表达式将这些不必要的字符过滤,代码如下:
def re(self,str):
'''编写正则表达式替换掉文中不需要的内容'''
pattern = r"<(.*?)>"
result = re.sub(pattern,'',str)
return result
过滤后内容如图所示:
如上图,所有的无关字符都已经被过滤掉,这就是我们最后想要的内容。至此,关于情书的爬取过程就写完了。但是因为每个类别的情书内容过于繁多,爬取速度慢,所以这里我用了多线程加快爬取速度。具体操作如下:
编写多线程爬取
这里我用的是双线程爬取,先定义两个线程类,然后分别调用爬虫类中的情书爬取方法,操作并不难,具体代码如下:
class one(threading.Thread):
def __init__(self,class_name):
threading.Thread.__init__(self)
self.class_name = class_name
def run(self):
py.Class(self.class_name)
class two(threading.Thread):
def __init__(self,class_name):
threading.Thread.__init__(self)
self.class_name = class_name
def run(self):
py.Class(self.class_name)
到此,爬取情书网所有情书就编写完成,使用双线程后爬取速度明显快了很多,总共才用了几分钟,以下是运行截图:
源代码
以下是全部的源代码,希望能帮助到各位
import requests
from bs4 import BeautifulSoup
import os
import re
import threading
class python:
'''
爬虫类:
负责爬取相应的信息
'''
def __init__(self):
self.url_frist = 'http://www.qingshu.so'
def page(self):
'''
爬取每个类别的链接和类别名
:return:
'''
data1 = requests.get(self.url_frist)
data1.encoding = 'utf-8'
soup = BeautifulSoup(data1.text, 'lxml')
reponse = soup.find_all(name='ul', attrs={'class': 'menulist'})
url_list = re.findall('<li><a href="(.*?)">.*?</a></li>', str(reponse))
title_list = re.findall('<li><a href=".*?">(.*?)</a></li>', str(reponse))
return url_list, title_list
def re(self,str):
'''编写正则表达式替换掉文中不需要的内容'''
pattern = r"<(.*?)>"
result = re.sub(pattern,'',str)
return result
def Class(self,class_name):
'''在每个类别中提取相关的信息'''
for k in range(1, 10):
if self.page()[0][self.page()[1].index(class_name)][0:-5] != '':
url2 = self.url_frist + self.page()[0][self.page()[1].index(class_name)][0:-5] + '_' + str(k) + '.html'
data2 = requests.get(url2)
data2.encoding = 'utf-8'
if data2.status_code == 200:
print(url2)
soup1 = BeautifulSoup(data2.text, 'lxml')
url_list_text = re.compile('<a href="(.*?)".*?>阅读详细»</a></div>').findall(str(soup1))
for url in url_list_text:
url3 = ''.join([self.url_frist, url])
text = requests.get(url3)
text.encoding = 'utf-8'
text_soup = BeautifulSoup(text.text, 'lxml')
text_reponse = text_soup.find_all(name='div', attrs={'id': 'artmain'})
text_reponse1 = text_soup.find_all(name='div', attrs={'class': 'a_content clearfix'})
#从得到的字符串里去掉不必要的字符
text_reponse2 = str(text_reponse1)[33:-7]
#调用正则表达式
text_main = self.re(str(text_reponse2))
#类别所在网页信息不同,所以定义条件,定义不同的正则表达选取相应的信息
if self.page()[1].index(class_name)<11:
text_title = re.compile('<a href=".*?">(.*?)</a></h1>').findall(str(text_reponse))
else:
text_title = re.compile('<h1 class="a_title">(.*?)</h1>').findall(str(text_reponse))
with open('情书/' + class_name + '/' + str(text_title[0]) + '.text', 'w', encoding="utf-8") as f:
f.write(str(text_main))
print('已成功爬取'+'《'+str(class_name)+'》的'+'《'+str(text_title[0])+"》文章")
else:
print(class_name + '的第' + str(k) + '页没有')
else:
print('这里的链接为空')
class one(threading.Thread):
def __init__(self,class_name):
threading.Thread.__init__(self)
self.class_name = class_name
def run(self):
py.Class(self.class_name)
class two(threading.Thread):
def __init__(self,class_name):
threading.Thread.__init__(self)
self.class_name = class_name
def run(self):
py.Class(self.class_name)
#处理目录
def save(title):
if not os.path.isdir('情书/' + title):
os.mkdir('情书/' + title)
return True
return False
if __name__ == "__main__":
py = python()
print(len(py.page()[1]))
for i in range(0,11):
class_name = py.page()[1][i]#取出爬取到类别的名字
if save(class_name):
t1 = one(class_name)
t1.start()
else:
print('此类别已下载完毕')
for i in range(11,22):
class_name = py.page()[1][i]
if save(class_name):
t2 = two(class_name)
t2.start()
else:
print('此类别已下载完毕')
总结
爬取到这些情书后,以后给女朋友写情书就不用发愁了。同时通过这次的爬取实践我温习了多线程的具体使用方法,也巩固了正则表达式的相关知识。不过我也发现了我的问题:命名。我对代码变量的命名这方面需要加强,以前我总是忽略这个问题,现在清楚的认识到,要想编写一段有质量的代码,需要严谨的态度,各方面都需要眼睛。此代码我个人认为编写的不错,当然还有许多改进的地方。具体的改进版我以后再写一下。