自学Python 来写一个爬虫吧

Python 爬虫

       目标:一本小说

      

首先,看一些基础方法:

1.     python打开一个网页

在python3中

import urllib.request #导包

conn = urllib.request.urlopen('https://www.baidu.com/s?&wd=python') #打开url连接
data = conn.read() #获取返回数据
data = data.decode('utf-8') #编码

其中

第1行 为导入urllib.request模块;

第3行 使用默认配置打开‘http://www.baidu.com’这个网页(http://是必须的

第4行 读取返回的数据

第5行 对数据进行编码处理(编码方式参考网页源代码 head下meta标签content下charset属性值)

最后获取的 data 就是网页上的数据

2.     字符串分割

上一步获取到了数据,但其中包含很多无用的数据,现在就是需要把有用的数据挑选出来

这里介绍一个string的方法str.partition(str1)方法,将str用str1划分为三段,生成一个列表。

        例:

>>>'abcdefghabcdefg'.partiton('c')
('ab','c','defghabcdefg')

在上一个方法的基础上我们可以获取我们想要的部分

3.     python操作文件

现在就是把获取到的数据写入文本了

with open('text.txt','a+') as docs:
    docs.write('1234567890')

其中 a+ 表示 文本方式读写打开,从文件结束位置继续写

4.     现在将这些方法集中起来我们就能保存想要的东西了


提升1:1.我想你一定不希望网络异常的时候你的程序停在那里一直等待:

导入socket包 设置socket.setdefaulttimeout(outtime)设置全局连接超时

导入time包 在打开连接失败后time.sleep(time),暂停一下 再次尝试

 

2.分割字符串简单有效,但在需要大量使用的时候就很难受了,你也可以尝试一下高级一点的方法:

例如你在分析了数个页面源代码后发现

小说标题 总是存在'<span class="newstitle"> 小说标题</span>'这样的地方

正文 总是存在'<divid="BookTextt"><br /> 小说内容</div>'这样的地方

并且在正文中总是有个别HTML标签的存在

导入re包,使用正则表达式:

retitle = '<span class="newstitle">([^<]+)<'

retext = '<divid="BookTextt"><br />([^(</div>)])</div>'

title = re.search(re.compile(retitle),data).group(1)#re.compile()预编译正则表达式 据说速度快一点 group(*)显示匹配子组

text = re.search(re.compile(retext),data).group(1)#对于group()不确定子组 使用groups()查看所有在选择

#清除空白字符当然也可以添加想要清楚的html标签

nullchar = ['<br>','<br />',' ',' ']#第四个空格其实是'&'n'b's'p';'

    for i innullchar :

        text =text.replace(i,'')



现在,我们就可尝试去扒一本小说了(假设免费但不提供下载)

       首先找到某小说在某网站的目录章节页面,比如这个:

              https://www.ybdu.com/xiaoshuo/13/13764/ #这只是我在百度上搜索某小说得到的结果

一般情况下我们能在它的 ul li 下找到某一章节的连接地址(一般情况下是相对URL需要处理)

它的其中一章的连接是像这样的

       https://somesite.com/*#&$/bookkindno/bookid/randomnum.html

那么

    rooturl= ‘https://somesite.com/*#&$/bookkindno/bookid/’

    reurl= ‘<li><a href="([0-9]{1,}.html)">[^<]</a></li>’#

    urllist= re.findall(re.compile(reurl),data).group(1)

    newlist= []

    for i in urllist : newlist.append(rooturl + i)



       newlist中就是该文章的所有章节的绝对URL

 

       接下来的工作就是按照newlist中的url去获取每一个页面,处理得到数据并存入文件,这种重复的任务交给程序处理是在合适不过了

 

 

       提升2:你一定发现你的程序扒取的速度实在是太慢了,它会一个个的去请求http,绝大部分时间是在等待响应,这真是极大的浪费,所以你可以试着使用多线程来处理这种情况。

       首先假设线程数为lines

       那么 先将newlist 分为lines份,一个比较好的方式是使用切片

             

sonlist= []

for i in range(lines):

    sonlist.append(newlist[i::lines])#[1,2,3,4,5,6][::2]-->[[1,3,5],[2,4,6]]

然后使用多线程去获取页面内容。

       先将之前单线程的过程整合成一个方法 比如:get_do_write(urllist)

 

import threading

#………………

sonlist= []

theadlines= []

for i in range(lines):

 sonlist.append(newlist[i::lines])

 threadlines.append(threading.Thread(target=get_do_write,args=(sonlist[i])))

for threadone in threadline:

 threadone.start()#所有线程启动

for threadone in threadline:

 threadone.join()#防止子线程未完成就被中断

这样就可以多个线程一起下载了

 

       提升3:上述方法存在一个很严重的问题,由于各个线程的速度不一致导致下载的章节是错乱的,另外一点就是有几率两个或以上的线程同时写入文件导致异常的出现。

       所以这里就对此进行进一步的解决。

       使用数据库作为中间缓存,存储章节的相对url数字部分(这下顺序就不会乱了),在写入数据库完成后,一起导入文本文件,同时在存入数据库的时候,对数据库操作加锁。

       这里推荐使用sqlite3数据库,理由很简单——简单!

       添加写入数据库的方法

       

import sqlite3

def createdb(bookid):

 with sqlite3.connect('config.db') as conn : #打开数据库,自动关闭连接

  c = conn.cursor() #创建游标

          sql = 'drop table if exists txt{0};'.format(bookno)

          c.execute(sql) #执行sql语句

          sql = 'create table txt{0}(id text not null,title text not null,txt textnot null);'.format(bookid)

          c.execute(sql) #执行sql语句

          conn.commit() #提交修改

def write2db(bookid,urlnum,title,text,times=0):

        if times < 3 : #失败重试

           try :

                lock = threading.Lock() #锁

                lock.acquire()#获取锁可以写入

                with sqlite3.connect('config.db') as conn :

                    c = conn.cursor()

                    sql= 'insert into txt{tablename}values("{id}","{title}","{txt}");'.format(tablename=bookid,id=urlnum,title=title,txt=text)

                    c.execute(sql)

                    conn.commit()

                lock.release()#释放锁

           except BaseException as e: #写入失败了?

                lock.release()#释放锁

                time.sleep(1)#休息一下

                write2db(bookid,urlnum,title,text,times=times+1) #失败重试

将上一个版本的写入文件改为写入数据库write2db(bookid,urlnum,title,text),并且在获取urllist的时候就创建表格(createdb(bookid))就可以了,这样就能够多线程的写入数据库,这样速度就会快很多。

       

def db2file(bookido):

       with sqlite3.connect('config.db') as conn :

           c = conn.cursor()

           sql = 'SELECT * FROM txt{0} ORDER BY id ASC'.format(bookid)

           txtlist = c.execute(sql)

           for onetxt in txtlist :

                write2file(onetxt[1]+’/n’+onetxt[2])#0 urlnum 1 title 2 text

       将之前的写入文件整合成write2file()方法即可(写入方式就可以覆盖写了)。

 

       如此就可正常的扒下来一本完整的小说了。这就是第一个使用的版本。

 

 

       提升4:有的时候当我们访问的太过频繁的时候,网站可能会拒绝你的请求,这个时候就需要使用代理了

              默认情况的我们使用的urllib.request.urlopen(url)#打开连接

使用代理:

       

proxy =urllib2.ProxyHandler({'http': 'ip:port',’https’:’ip2:port2’}) # http代理和 https 代理

opener= urllib2.build_opener(proxy)

conn= opener.urlopen(url)

现在你就可以使用代理来扒取小说了(也可以设置多个代理,使用随机数挑选代理连接服务器)

 

       提升5:完成第一个目标以后,如果你还有兴趣就可尝试使用面向对象的思想去重写整个程序了,并考虑该程序对多个网站的兼容性等等,这里就自己发挥了。

       这里放上我的结构图,希望大佬们能给点意见和建议

+Site #网站类 针对不同的网站有不同的正则表达式,不同的模版,是否使用代理等等

|      __init__() #初始化方法 从数据库导入该网站的信息(这里与下载的内容不在一个数据库中,sqlite3这点好,没服务,不吃资源,很适合访问量不多的情况)

∟    _getsite() #从数据库获取网站信息,供初始化调用

+Book #小说类 定义一本小说

|      __init__() #初始化方法 导入书本基本信息

|      _site #站点属性 (后面你就知道有什么用了)

|      _createtable() #创建表 没表就新建表

|      db2file() #写入文件

∟    _selectrow() #查询数据库已有的章节数 判断是否需要重新下载

+Html #页面 (这里可以创建UrlistHtml和TextHtml子类 分别实现有效数据的选取)

|      _book #book属性

|      gethtml()# 获取页面数据 通过self._book._site 获取配置

∟    html2list()#通过页面获取章节列表 通过self._book._site 获取re表达式和partition分割字符串

|      html2text()#获取每一张的数据生成Novel,并将book属性赋予Novel

∟    _url() #相对url转化

+Novel #章节类

|      __init__() #

|      _book #

∟    novel2db() #存入数据库

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值