Python爬虫练习(五)


前言

Python爬虫练习(一)作出改进,增加了多线程,使用re代替BeautifulSoup查找,提高爬取速度,改进了进度条,及时捕获异常,防止中途换行。


代码

引入库

import requests
from bs4 import BeautifulSoup
import re
import threading #线程
import queue #队列
import time
import random

定义线程类

创建线程后执行WriteItem()函数查找元素并写入文件。

class myThread(threading.Thread):
    def __init__(self,name,q):
        threading.Thread.__init__(self)
        self.name=name
        self.q=q
    def run(self):
        WriteItem(self.name,self.q)

开始线程

创建10个线程,若URL列表小于10,则创建列表长度个线程。

def StartThread():
    workqueue=queue.Queue(number)
    for i in range(len(ls)):
        workqueue.put(ls[i])
    for i in range(min(10,len(ls))):
        thread=myThread('T-'+str(i+1),workqueue)
        thread.start()
        threads.append(thread)
    for thread in threads:
        thread.join()

进度条

用来展示进度,接收info信息,展示线程名称、状态和捕获到的异常。

def Print(i,info):
    global start
    p=round(30*i/len(ls))
    p1='█'*p
    p2='—'*(30-p)
    dur=time.perf_counter()-start
    print("\r{}{} {}/{}".format(p1,p2,i,len(ls)),end='  ')
    print("{:.1%}".format(i/len(ls)),end='  ')
    print("用时{:.2f}s".format(dur),end='  ')
    print("状态:{}".format(info),end=' ')

选择请求头

储存请求头。

def GetAgent():
    agent=[ 'Opera/8.0 (Windows NT 5.1; U; en)',\
           'UCWEB7.0.2.37/28/999',\
           'NOKIA5700/ UCWEB7.0.2.37/28/999',\
           'Openwave/ UCWEB7.0.2.37/28/999']
    return agent

发起请求

返回响应,若超时,则返回空。

def GetHtml(url):
    try:
        r=requests.get(url,headers={'User-Agent':random.choice(GetAgent())},timeout=30)
        r.raise_for_status()
        return r.text
    except:
        return ''

查找列表

返回URL列表。

def FindList(number,demo):
    soup=BeautifulSoup(demo,'html.parser')
    flag=0
    for tag in soup('a'):
        try:
            s=re.match(r'[\u4e00-\u9fff]{2,5}',tag.attrs['title']).group(0)
            if s=='妙蛙种子':
                flag=1
            if re.match(r'第.世代',s):
                continue
            if flag:
                ls.append('https://wiki.52poke.com/wiki/'+s)
            if len(ls)==number:
                break
        except:
            continue

获取列表

尝试三次,若列表获取失败,抛出异常,结束程序。

def GetList(number):
    url="https://wiki.52poke.com/wiki/宝可梦列表%EF%BC%88按全国图鉴编号%EF%BC%89/简单版"
    for i in range(3):
        FindList(number,GetHtml(url))
        if len(ls)==number:
            print('列表提取成功')
            break
        else:
            print('列表提取失败{}/3'.format(str(i+1)))
    else:
        raise Exception('列表提取失败')

查找元素

用re替代BeautifulSoup,减少查找时间,分别进行try-except,查找失败则返回"–"。

def FindItem(demo):
    try:
        num=re.search(r'title="宝可梦列表(按全国图鉴编号)">#[0-9]{3}',demo).group(0)
        num=num.split(">")[1] 
    except:
        num="--"
    try:
        atrs=re.search(r'roundy a-r at-c .+',demo).group(0).split()
        atr1=atrs[3].split('-')[-1]
        atr2=atrs[4][:-1].split('-')[-1]
        if atr1==atr2:
            atr=atr1
        else :
            atr=atr1+','+atr2
    except:
        atr="--"
    try:
        cla=re.search(r"<td class=\"roundy b.+ bgwhite bw.+\">[\u4e00-\u9fff]{1,4}宝可梦",demo).group(0)
        cla=cla.split(">")[1]   
    except Exception as e:
        cla="--"
        print(e)
    return num,atr,cla

写入文件

多线程查找,写入,使用线程锁将公共资源(全局变量,输出,写入)夹住,通过sleep控制爬取频率,将连接失败和查找失败的异常分开,记录未成功写入的URL链接,通过进度条函数展示信息。

def WriteItem (name,q):
    global f,suc,count
    while True:
        try:
            url=q.get(timeout=2)
        except:
            break
        try:
            demo=GetHtml(url)
            if demo=='':
                raise Exception('连接失败')
            num,atr,cla=FindItem(demo)
            if atr=='--' and cla=='--':
                raise Exception('查找失败')
            nam=url.split('/')[-1]
            pa='{0:{4:}<4}{1:{4:}^10}{2:{4:}^9}{3:{4:}^10}'
            lock.acquire()
            f.write('\n'+pa.format(num,nam,atr,cla,chr(12288)))
            suc+=1
            count+=1
            info='{}:查找成功,已写入{}项'.format(name,suc)+' '*5
            Print(count,info)
            lock.release()
            time.sleep(random.random()*5)
        except Exception as e:
            lock.acquire()
            lt.append(url)
            count+=1
            info=name+':Error:'+str(e)+' '*5
            Print(count,info)
            lock.release()

主函数

每轮查找过后,重新查找失败的链接,直到所有链接成功或完成五轮查找。

def main():
    global f,suc,sucs,count,ls,lt
    pa='{0:{4:}<4}{1:{4:}^10}{2:{4:}^9}{3:{4:}^10}'
    #写入文件
    f.write(pa.format('序号','名称','属性','分类',chr(12288)))
    for i in range(5):
        print("第{}次查找".format(i+1))
        StartThread()
        print('')
        sucs+=suc
        if len(lt)==0:
            break
        ls=lt
        lt=[]
        suc=0
        count=0

执行

初始化全局变量,先获得URL列表,再执行主函数。

number=eval(input("输入查找数量:"))
start=time.perf_counter() #开始计时
lock=threading.Lock() #定义线程锁
ls=[] #URL列表
lt=[] #查找失败的URL
threads=[] #线程列表
suc=0 #单轮查找成功的数量
sucs=0 #查找成功的总量
count=0 #查找进度
GetList(number)
path='C:/Users/lenovo/Desktop/py/其他/pokemon-m.txt'
f=open(path,'w')
main()
f.close()
dur=time.perf_counter()-start
print('程序结束')
print('共写入{}项,失败{}项,用时{:.2f}s'.format(sucs,number-sucs,dur))
time.sleep(10)

排序

多线程爬取返回的结果为乱序,重新排序的方法与Python爬虫练习(二)中的相同,不再赘述。

结果

运行情况在这里插入图片描述
运行结果
在这里插入图片描述
排序结果
在这里插入图片描述

总结

requests使用多线程之后效率得到了很大的提升,使用多线程时应注意用线程锁将公共资源夹起,防止各线程之间相互干扰。使用多线程在爬取多页面时速度可以与scrapy相当,但应注意爬取频率,过高的频率可能导致查找失败甚至ip被封,在调试时我就被封了三个小时。线程数目的选择对爬虫效率也有影响。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值