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),有时候还是无法定位元素,个人感觉是网速问题,有大佬知道也希望告知!!