小爬爬的进化史---小白的爬虫养成

本文记录了作者从零开始学习爬虫的过程,从简单的图片下载,到尝试爬取小说全文,再到优化代码实现小爬爬的瘦身,最后运用多线程让爬虫实现并行下载。作者强调了在学习过程中遇到的问题和解决方案,以及对正则表达式的偏好。这是一个关于Python爬虫学习和进阶的故事。
Python3.8

Python3.8

Conda
Python

Python 是一种高级、解释型、通用的编程语言,以其简洁易读的语法而闻名,适用于广泛的应用,包括Web开发、数据分析、人工智能和自动化脚本

1、小爬爬的诞生

前两天看大神的爬虫文章,自己照着写了一个。经过多次修改,和之前的就大不一样了。
开始只是简单的图片下载,从一个页面中下载所有的图片,代码如下:

import re
from bs4 import BeautifulSoup
import requests

def get_html(url):
    headers={'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.68 Safari/538.36'}
    response=requests.get(url,params=headers)
    if response.status_code==200:
        print("状态码成功")
        if "验证" in response.text:
            print("被验证")
            return None
        else:
            return response.text
    else:
       print("状态码失败"+str(response.status_code))
            
def parse_html(html,name):
    soup=BeautifulSoup(html,'lxml')
    results=soup.select(name)
    with open('img\\黄图地址.txt','w') as f:
        for result in results:
            print(result['src'])
            f.write(result['src']+'\n')
            
    
url='https://www.55df.xyz/pic/5/2020-01-10/25477.html'
html=get_html(url)
if html==None:
    print('none')
    pass
else:
    parse_html(html,name='img')
    with open('img\\黄图地址.txt','r')as f:
        readlines=f.readlines()
        lens=len(readlines)
        print(lens)
        for i in range(lens):
            url=readlines[i].rstrip()
            print(url)
            filename='img\\'+str(i+1)+'.jpg'
            with open(filename,'wb')as f1:
                print("正在下载{}".format(i+1))
                f1.write(requests.get(url).content)
                print("下载完成")

注意:里面的链接地址请自行无视,
如由此引发局部问题请自行解决~ ┗( ▔, ▔ )┛

2、小爬爬的兄弟个子更高吗?

不满足于光爬图片,转眼看到兄弟手机上的“笔趣阁”,露出了迷人的微笑。
在这里插入图片描述
兄弟:你奏凯~我不是这种人!
哈哈,在兄弟略显失落的眼神中(为啥是失落?)我开始了给小爬爬生个弟弟的行动(纳尼?生?)|ू・ω・` )

import re
from bs4 import BeautifulSoup
import requests,time
from urllib import parse

#获得页面文件
def get_html(url):
    headers={'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.68 Safari/538.36'}
    response=requests.get(url,params=headers)
    response.encoding = "utf-8"
    if response.status_code==200:
        print("状态码成功")
        return response.text
    else:
       print("状态码失败"+str(response.status_code))

      
#将获得的章节文件链接保存下来,为了调试和审查,先保存下来再说          
def parse_html(html,name):
    global url
    #树化页面文件
    soup=BeautifulSoup(html,'lxml')
    #找到章节列表
    results=soup.select(name)
    #这个是为了后面对链接的修改,将相对路径变成绝对路径
    page_url = url
    #创建链接的列表
    rurls = list()
    with open('book地址.txt','w') as f:
        for result in results:
            #获得章节名称,当时为了核对获得的链接才写的,现在感觉也没啥用
            rname =result.text
            #获得章节列表,并把相对路径换成绝对路径。使用parse需要先引入urllib
            rurls.append(parse.urljoin(page_url,result['href']))
            #print([rname,parse.urljoin(page_url,result['href'])])
            #将章节链接写入文件里,其实这个文件没啥用,也是调试和审查的时候用的
            f.write('"'+rname+'":"'+parse.urljoin(page_url,result['href'])+'"\n')
    #返回章节链接列表
    return rurls


#获得章节的内容     已废弃,没有删除
def get_content(url):
    #引用全局变量:书名
    global bookname
    #获得章节页面文件
    conhtml = get_html(url)
    #实例化这个页面
    soup=BeautifulSoup(conhtml,'lxml')
    获得章节名称
    title = soup.select('.bookname h1')[0].text
    #创建待写入文件,以章节名为文件名,放在book文件夹里
    with open('book\\'+title+'.txt','w',encoding="utf-8") as f:
        #写入章节标题
        f.write(title+'\n')
        #获得章节内容
        contents = soup.select('div #content')
        #写入章节内容
        for content in contents:
            f.write(content.text+'\n')

#新写的获取章节内容的函数
def get_book(url):
    global bookname
    wl = True
    wln = 0
    #读取时总是出现错误中断,写了这个防止中断
    while wl == True: 
        try:
            conhtml = get_html(url)
            wl = False
        except:
            print('erro:',wln)
            if wln >10:
                wl=False
            wln += 1
    
    soup=BeautifulSoup(conhtml,'lxml')
    title = soup.select('.bookname h1')[0].text
    print(title)
    #以书名为文件名,创建一个可追加的文件,每次向后写入各章节内容
    with open('book\\'+bookname+'.txt','a',encoding="utf-8") as f:
        f.write(title+'\n')
        contents = soup.select('div #content')
        for content in contents:
            #content.p.clear()
            #wcon = content.text
            #wcon = wcon.replace('<br>','<p>')
            #print(wcon)
            #content.text.replace('    ','\r\n')
            f.write(content.text+'\n')        

#获得书的名字
def get_bookname(url):
    html = get_html(url)
    soup=BeautifulSoup(html,'lxml')
    names = soup.select('#info h1')
    return names[0].text

#废弃,没有删除
def get_url(url,n,m):
    urls = list()
    if n == 0:
        urls.append(url)
    for i in range(n,m):
        iurl = url+'index_{}.html'.format(i+2)
        urls.append(iurl)
    return urls
            
#&nbsp;&nbsp;&nbsp;&nbsp;就是因为,八王子在十岁的时候,便开启“神武印记”,现在已经是黄极境后期的年轻武者。


url='https://www.xsbiquge.com/75_75151/'
'''
indexs = get_url('https://www.55df.xyz/text/2/',6,10)
print(indexs)

unumber = 0
for url in indexs:
    
    html=get_html(url)
    if html == None:
        continue
    urls = parse_html(html,'.channel ul li a')
    for url in urls:
        unumber += 1
        print(unumber)
        get_content(url)

'''
'''
#html = get_html(url)
#results=parse_html(html,'#list a')
conhtml = get_html('http://www.xbiquge.la/7/7552/3449774.html')
soup=BeautifulSoup(conhtml,'lxml')
contents = soup.select('div #content')
#title.clear()
for content in contents:
    content.p.clear()
    content.text.replace('<br>','\n')
    print(content.text)
'''

#get_content('http://www.xbiquge.la/7/7552/3449774.html')
bookname = get_bookname(url)
html = get_html(url)
urls=parse_html(html,'#list a')
for url in urls:
    get_book(url)


看吧看吧,这个代码把书全部下载到一个文件里,结果运行时间好长啊。ヾ(=・ω・=)o网速不给力,下载速度也没法很快。一个3000章以上的小说,要下几个小时…………
在这里插入图片描述

3、小爬爬瘦身计划

好吧,坐着等着无聊,又给小爬爬找了个兄弟,功能一样,只是换了个写法(✪ω✪)


import requests
import os
from lxml import html
from urllib import parse

url = 'https://www.xsbiquge.com/99_99638/'

#获得页面文件
def get_html(url):
    page = requests.get(url).content.decode('utf-8')
    strhtml = html.fromstring(page)
    return strhtml

#获得章节链接列表,顺便获得书名
def get_booklist(strhtml):
    bookname = strhtml.xpath('//*[@id="info"]/h1/text()')[0]
    listurls = [parse.urljoin(url,i) for i in strhtml.xpath('//*[@id="list"]/dl/dd/a/@href')]
    return bookname,listurls


#将将各章节内容保存到一个文件中,文件名为书名
def get_bookcontent(conurl,bookname):
    nhtml = get_html(conurl)
    nname = nhtml.xpath('//*[@class="bookname"]/h1/text()')[0]
    ncont = nhtml.xpath('//*[@id="content"]/text()')
    if not os.path.exists('book'):
        os.makedirs('book')
    with open('book\\{}.txt'.format(bookname),'a',encoding="utf-8") as b:
        b.write(nname+'\n')
        for c in ncont:
            c = c.replace('<!--go-->','')
            c = c.replace('<!--over-->','')
            b.write(c+'\n')
    return nname

strhtml = get_html(url)

bookname,listurls = get_booklist(strhtml)


for conurl in listurls:
    nname = get_bookcontent(conurl,bookname)
    print(nname+':OK')

是不是比之前的简洁一些,可是Lxml用起来很别扭,无法实例化一个页面文件,只能进行字符串的查找,虽然有xpath,但有些页面获得xpath不是很准确。最重要的是,我不喜欢正则!我不喜欢正则!我不喜欢正则!重要的事情说三遍!
(其实就是不会ψ(*`ー´)ψ)

4、小爬爬的分身术

今天用了一天的时间,看多线程的东西,多谢几位大神,给了我灵感和启发。所以让小爬爬可以变身了!究极进化?
在这里插入图片描述

import threading
import requests,os,time
from lxml import html
from urllib import parse

#创建Lock()给数组加锁
gLock = threading.Lock()

#公用数组,记录章节下载链接
book_link_lst = []

#公用字典保存book信息
#key:'book_name','index_link','book_path','book_files','thread_isover'
book_info = dict()

#获取html文件
def get_html(url):
    page = requests.get(url).content.decode('utf-8')
    strhtml = html.fromstring(page)
    return strhtml

#获取章节链接返回数组[[index,连接],]
def get_booklist(strhtml):
    rlst = list()
    #书名写入公用字典
    book_info['book_name'] = strhtml.xpath('//*[@id="info"]/h1/text()')[0]
    listurls = [parse.urljoin(book_info['index_link'],i) for i in strhtml.xpath('//*[@id="list"]/dl/dd/a/@href')]
    return list(zip(range(len(listurls)),listurls))

#生产函数,获取章节链接,放入公用数组
def crate_link(url):
    #链接写入公用字典
    book_info['index_link'] = url
    #初始化结束链接数
    book_info['thread_isover'] = 0
    #获得索引页页面文件
    page = get_html(url)
    #获得章节列表,返回的项里包含链接在列表中的顺序index,为以后的排序做准备
    book_links = get_booklist(page)
    for b in book_links:
        #章节列表追加入公用列表
        book_link_lst.append(b)

#文件合并
def merge_file():
    #获得文件路径
    tmpfiledir = book_info['book_path']
    #获得文件列表    路径+文件名
    tmpnameList = [tmpfiledir+'/'+i for i in os.listdir(tmpfiledir)]
    #文件列表写入公用字典
    book_info['book_files'] = tmpnameList
    #文件排序,文件名前面已经带序号,所以排序不会乱
    tmpnameList.sort()
    #获得文件名
    tmpbookname = book_info['book_name']
    #创建合并文件
    tmpbookfile = open('book/{}.txt'.format(tmpbookname),'a',encoding = 'utf-8')
    #打开文件列表,并读取文件到新文件
    for i in tmpnameList:
        x = open(i,'r',encoding = 'utf-8')
        tmpbookfile.write(x.read())
        x.close()
    tmpbookfile.close()

#创建多线程
class Down_books(threading.Thread):
#下载章节内容并保存在文件夹里
    def run(self):
        doit = True
        while doit:
            #加锁
            gLock.acquire()
            if len(book_link_lst) == 0:
                gLock.release()
                continue
            else:
                #获得链接字符串和编号 (index,url)
                tmpindex,tmpurl = book_link_lst.pop()
                #解锁
                gLock.release()
                #获得章节页面
                nhtml = get_html(tmpurl)
                #获得章节名称
                nname = nhtml.xpath('//*[@class="bookname"]/h1/text()')[0]
                #获得章节内容
                ncont = nhtml.xpath('//*[@id="content"]/text()')
                #生成保存目录
                book_info['book_path'] = 'books\\{}'.format(book_info['book_name'])
                #查询目录是否存在,否则创建书名的目录
                if not os.path.exists(book_info['book_path']):
                    os.makedirs(book_info['book_path'])
                #生成文件名,前面加上章节的index,0不够就补够,目的是为了将各章节文件排序,然后合并。不排序就乱了
                tmpfilename = '0'*(5-len(str(tmpindex)))+str(tmpindex)+nname
                #保存章节内容到文件
                with open('books\\{}\\{}.txt'.format(book_info['book_name'],tmpfilename),'w',encoding="utf-8") as b:
                    b.write(nname+'\n')
                    for c in ncont:
                        c = c.replace('<!--go-->','')
                        c = c.replace('<!--over-->','')
                        b.write(c+'\n')
                print('下载完成:',nname)
                #如果待下载链接列表空了,就结束这个循环,完结这个线程
                if len(book_link_lst) == 0:
                    book_info['thread_isover'] += 1
                    print('线程结束')
                    #结束循环
                    doit = False

#启动线程前的准备工作
crate_link('https://www.xsbiquge.com/70_70685/')

#启动线程 5个
for x in range(5):
    Down_books().start()

#循环查询待下载列表是否为空,如为空则开始合并文章
Down_doing = True
while Down_doing:
    if book_info['thread_isover'] == 5:
        merge_file()
        Down_doing = False
        isdel = input('文件已合并,是否删除各章节文件?y/n:')
        if isdel == 'y':
            for i in book_info['book_files']:
                os.remove(i)
        print('文件删除完毕')
        print()
    else:
        time.sleep(10)






一个简单的多线程下载,为啥要用分章节下载后再合并呢?因为多线程无法控制章节下载完成的先后顺序,如果下载后放入一个文件里,那先后顺序不同就全乱套了。

目前我的小爬爬就只是进化到这里,之后还要再继续学习才行。
多看看大神们的巨著,多做些练习~~~
一起加油学习吧!!!
在这里插入图片描述
今天努力的你,头发又掉了多少?

您可能感兴趣的与本文相关的镜像

Python3.8

Python3.8

Conda
Python

Python 是一种高级、解释型、通用的编程语言,以其简洁易读的语法而闻名,适用于广泛的应用,包括Web开发、数据分析、人工智能和自动化脚本

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值