Python爬虫:笔趣阁小说搜索和爬取
0x00 写在前面
最近开始学习Python的爬虫,就试着写了写笔趣阁小说的爬虫,由于是初学,所以正则,bs4,xpath都用了用,下面是正文
0x01 搜索页面
首先是对搜索页面的分析,网址如下:
http://www.xbiquge.la/modules/article/waps.php
先对查找方式进行测试

f12查看header

是post方法,进一步查看数据

发现数据名是searchkey,内容并没有进行加密
开始写代码
murl = 'http://www.xbiquge.la/modules/article/waps.php'
headers = {"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:77.0) Gecko/20100101 Firefox/77.0"}
data={'searchkey':book}
response=requests.post(url=murl,data=data,headers=headers)
response.encoding = "utf-8"
code=response.text
这部分代码是写在函数getBook()里的,其中book是向函数传入的值,response.encoding="utf-8"这里是为了让code中的中文可以正常显示
然后我们接着分析网页

我们发现这里的搜索结果都写在这个table里,且书名的标签是td,class为even,作者也在 td class=“even” 中,这就好办了,我们可以先找到class=even的所有标签,接下来我们可以用bs4和正则表达式进行元素定位
code=response.text
soup=BeautifulSoup(code,'lxml')
tab=soup.select('.even')
all=re.findall(r'<td class="even">(.*?)</td>',str(tab))
if len(all)==0:
return None
这里是接着上边的函数写的,tab是用bs4定位到的所有class=even的标签源码,再接着用正则获得我们想要的内容,当然,这里得到的all的数据并不符合我们想要的数据形式,先回到网站

我们可以发现,由于这里的区别,我们就能区分开书名,作者和网址,首先我们用正则把url和书名拿到,而all中的所有数据都是even的,作者单独占了一个带有even的标签,我们输出all后不难发现,第1,3,5…即
i%2!=0
的数据都是作者的,所以我们有
name=re.findall(r'target="_blank">(.*?)</a>',str(all))
author=[]
url=re.findall(r'href="(.*?)"',str(all))
for i,n in enumerate(all):
if i%2!=0:
author.append(n)
for i in range(len(name)):
if i == 0:
print('序号\t书名\t作者\t网址')
print('['+str(i)+']\t'+name[i]+'\t'+author[i]+'\t'+url[i])
burl=input("请输入你想获得txt的书的序号:")
return url[int(burl)]
最后我们输出得到的数据,并要求用户输入对应序号,并返回相应书籍的url,getBook()就完成了
0x02 章节获取
现在我们已经从搜索页面得到了我们想要的小说的url,下一步就是得到小说的每一个章节的url了,即getChap(), 先去网站实地考察一下


发现章节的名称和网址都在 div id=“list” 下,小说名在 div id=“info” 下(为什么这里要在获得一遍小说名呢,别问,问就是上个函数的返回值忘写了)
接下来我们用xpath方法来定位元素
headers = {"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:77.0) Gecko/20100101 Firefox/77.0"}
response = requests.get(url=url,headers=headers)
response.encoding = "utf-8"
code = response.text
tree=etree.HTML(code)
nurl = tree.xpath('//div[@id="list"]/dl/dd/a/@href')
name=str(tree.xpath('//div[@id="info"]/h1/text()')).split("'")[1]
print(name+"共发现"+str(len(nurl))+"章,即将开始爬取")
xpath定位确实非常方便,定位标签后用@可以得到标签的元素值,/text()可以得到标签的文本内容,最后
for i in range(len(nurl)):
turl='http://www.xbiquge.la'+nurl[i]
getContent(turl,name)
print("爬取完成,剩余" + str(len(nurl)-i-1)+'章')
再对得到的href值进行拼接得到url,再用for和getCotent()(代码在下面)进入不同的章节网址进行内容提取
0x03 章节内容获取
接下来就是getContent()了,我们还是先看看网站

好的,内容在 div id=“content” 下,而且 div class=“bookname” 下有当前章节的章节名,开始定位并获取数据,代码如下:
headers = {"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:77.0) Gecko/20100101 Firefox/77.0"}
response = requests.get(url=url, headers=headers)
response.encoding='utf-8'
time.sleep(1)
code = response.text
tree = etree.HTML(code)
chap = tree.xpath('//div[@class="bookname"]/h1/text()')[0]
content = tree.xpath('//div[@id="content"]/text()')
好嘞,chap就是章节名,content就是章节内容,这里的/text()很有意思,它返回的是一个跟这个xpath匹配的所有元素构成的列表,所以chap那里的[0]代表这匹配的第一个元素,也就是章节,content同理,我们先看后面的写入文件的代码
with open(name + '.txt', 'a+', encoding='utf-8') as file:
print("开始爬取:" + chap)
file.write(chap + '\n\n')
for i in content:
text = str(i)
text.strip()
file.write(text)
file.write("\n\n")
file.close()
这里的name是小说名称,直接传入函数的,a+代表有文件的话我们从文件尾部写入,encoding=“utf-8”保证中文不乱码,先写入章节名,接下来的foreach循环,我们结合网站源码来分析

我们可以看到在这个div下有很多用于换行的br标签,而/text()的好处就在于,它不会读入br标签,所以
content = tree.xpath('//div[@id="content"]/text()')
这句代码得到的列表content,其中的元素是从一个br(或从头)开始,到下一个br(或结尾)前的中间这段文本,同时,div的子标签中的文本我们是不会得到的,这也就避免的下图的广告被读入的情况(笑)

0x04 完整代码
import requests
import re
from bs4 import BeautifulSoup
from lxml import etree
import time
from pip._vendor.retrying import retry
def getBook(book):
murl = 'http://www.xbiquge.la/modules/article/waps.php'
headers = {"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:77.0) Gecko/20100101 Firefox/77.0"}
data={'searchkey':book}
response=requests.post(url=murl,data=data,headers=headers)
response.encoding = "utf-8"
code=response.text
soup=BeautifulSoup(code,'lxml')
tab=soup.select('.even')
all=re.findall(r'<td class="even">(.*?)</td>',str(tab))
if len(all)==0:
return None
name=re.findall(r'target="_blank">(.*?)</a>',str(all))
author=[]
url=re.findall(r'href="(.*?)"',str(all))
for i,n in enumerate(all):
if i%2!=0:
author.append(n)
for i in range(len(name)):
if i == 0:
print('序号\t书名\t作者\t网址')
print('['+str(i)+']\t'+name[i]+'\t'+author[i]+'\t'+url[i])
burl=input("请输入你想获得txt的书的序号:")
return url[int(burl)]
def getChap(url):
headers = {"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:77.0) Gecko/20100101 Firefox/77.0"}
response = requests.get(url=url,headers=headers)
response.encoding = "utf-8"
code = response.text
tree=etree.HTML(code)
nurl = tree.xpath('//div[@id="list"]/dl/dd/a/@href')
name=str(tree.xpath('//div[@id="info"]/h1/text()')).split("'")[1]
print(name+"共发现"+str(len(nurl))+"章,即将开始爬取")
for i in range(len(nurl)):
turl='http://www.xbiquge.la'+nurl[i]
getContent(turl,name)
print("爬取完成,剩余" + str(len(nurl)-i-1)+'章')
@retry
def getContent(url,name):
headers = {"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:77.0) Gecko/20100101 Firefox/77.0"}
response = requests.get(url=url, headers=headers)
response.encoding='utf-8'
time.sleep(1)
code = response.text
tree = etree.HTML(code)
chap = tree.xpath('//div[@class="bookname"]/h1/text()')[0]
content = tree.xpath('//div[@id="content"]/text()')
with open(name + '.txt', 'a+', encoding='utf-8') as file:
print("开始爬取:" + chap)
file.write(chap + '\n\n')
for i in content:
text = str(i)
text.strip()
file.write(text)
file.write("\n\n")
file.close()
book=input("请输入你要获取的书名或作者(请您少字也别输错字):")
url=getBook(book)
if url==None:
print("未找到相关书籍")
else:
getChap(url)
print("爬取完毕")
要问我为什么用retry?因为就算sleep(1),有时候还是无法定位元素,个人感觉是网速问题,有大佬知道也希望告知!!