记录一次爬取某昵称网站的爬虫

同学跑去实习了...然后工作的时候要她用python写一个爬虫,爬取一万个可以用的用户昵称。(为什么他们都能找到工作啊QAQ)

然后,她找到了我...然后在我动笔的时候,发现之前写过的爬虫基本上忘完了...无奈下只好对着以前写的项目,重新找了下文章,现在写一篇文章重新集合下之前零散的知识点。

我这里写的内容只是针对自己的需求写的,如果想要彻底了解BeautifulSoup的用法的话,可以参考下这篇文章: () => 文章 (看完这文章我都想敲一下Java8新增的lambda表达式了...真的超级炫酷的)

跳过基础内容讲解的话,点我

言归正传,开始吧。


python的爬虫,可能会用到两个包(过去是这样的),一个是BeautifulSoup,一个是etree

我用我的理解简单说说这两个包吧(这里的话十有八九别信...)

BeautifulSoup:

  • 一个抓取页面的插件,能够页面抓取出来,能对页面数据做简单的筛选
  • 引入方法: from bs4 import BeautifulSoup
  • 安装方法: pip install bs4

etree:

  • 在3.7(应该是3.7)版本以前,都是特别好用的,因为里面可以用XPath直接锁定DOM元素,但是在未来的更新中,它对XPath的兼容性并不好,所以干脆砍掉了。注:在谷歌浏览器里可以直接复制出页面的XPath,所以个人感觉没有必要去记XPath语法,毕竟我们可不是因为玩爬虫而玩python的,要知道,python可是因为人工智能而一鸣惊人。
  • 引入方式: from lxml import etree(这个词应该是Element Tree)
  • 安装方式: pip install lxml
  • 据说在4.1.1版本里可以通过from lxml.html import etree使用etree
  • 想要使用的话,可以用pip指定版本安装,安装过去的版本使用

因为我目前不是很想在这爬虫上折腾,所以这次就用了BeautifulSoup写完了本次的爬虫,这里就不对etree的用法做介绍了,想了解的话根据自己安装的lxml版本换一篇文章,就不浪费时间了

先来说说BeautifulSoup的四种数据类型,为了以后做铺垫:

  1. Tag
  2. NavigatableString
  3. BeautifulSoup
  4. Comment

Tag => 就是页面里的标签;

NavigableString => 如果你想获取Tag中的文字,你可以用TagName.string获取里面的内容,获取的值便是NavigableString类型的对象;

BeautifulSoup => 它是个功能更多的对象,你可以使用更多的方式获取子类的对象,获取方式很简单,举个栗子:

soup = BeautifulSoup(你就想象这里有很长很长很长的html代码吧, 'lxml')  # 获取页面文档 这是个BeautifulSoup类型的数据

# 如果愿意的话,可以用 print(type(soup)) 检查下 soup 的数据类型,我没有测试

item = soup.find_all(name='a', attrs={'class': 'hover', 'target': '_self'})  
# 这里用的 find_all 方法,意思就是找到之前获取的文档对象(soup)下的所有满足<a class='hover' target='_self'></a>的对象

Comment => 这个属性比较特殊了,它可以找到所有的注释内容...没用过

对于BeautifulSoup对象有很多属性可以用,比如说:

  • 靠标签名查找 => soup.select('div')
  • 靠选择器查找 => soup.select('.top-bar')
  • 靠id查找 => soup.select('#app')
  • 组合的 => soup.select('#app .top-bar')
  • 子类的 => soup.select('#app > .top-bar')
  • 具体 => soup.select('div[data="NickName" class="name"]') # 这里可以了解下Emmet语法,挺像的...方括号里的是属性,大括号里的是内容,中间不能有空格。

参考的文章 => 现在想想...我当初为什么没有用select来写...

基础东西讲完了...后面的感觉完全不用看了,如果你闲着没事干的话

正式开始上代码

靶机 (用靶机这个词是不是感觉特别装逼啊)

http://www.oicq88.com

思路

  1. 获取到页面元素

  2. 检查下文档页面后,发现它把昵称分成了很多类别,一共五十多个,点进去之后,会进入子域名,使用字符串拼接直接访问内部链接即可

  3. 每一个类别里有很多页数据,我们需要优先知道页面总数,才可以去遍历,至少不会出现角标越界的状况,无意间发现,在页面后面拼接的数字超出是不会报错的,并且能看到的页面是该网站的最后一页(我在说什么啊) | 其实还有个思路,爬完一页之后判断是否还有下一页,如果有,则继续向后遍历,没有则退出循环。

  4. 利用BeautifulSoup获取到所有的昵称内容(为什么不能前后台分离用,前台向后台请求json数据,这样我就能直接拿到所有的昵称了...也不知道这个是伪静态还是静态)

  5. IO流,文件的写入

大纲

  1. 拿到页面元素,也就是document文档
  2. 拼接得到所有昵称类型
  3. 分析状况
  4. 拿到每种类型的页数
  5. 遍历每一页,并将数据写入文件中

拿到拿到页面元素

拿页面元素,很简单的

response = requests.get(url='http://www.oicq88.com')

不过我定义了个函数的说...

import requests  # 别忘了导包,报错的话装包 pip install requests,pip版本过低的话更新下,会有提示的

# 模拟用户从浏览器登录
def get_html(url):
    headers = {
        'User-Agent': 'Mozilla/5.0(Macintosh; Intel Mac OS X 10_11_4)\
        AppleWebKit/537.36(KHTML, like Gecko) Chrome/52 .0.2743. 116 Safari/537.36'
    }  # 模拟浏览器访问

    response = requests.get(url, headers=headers)  # 请求访问网站
    html = response.content.decode()  # 获取网页源码 因为编码问题,我们看不懂机器的语言,所以需要先解码

    return html

然后为了方便,我在函数下面加了点全局使用的属性

base_url = 'http://www.oicq88.com'  # 基本域名
file_name = 'result.txt'  # 输出的文件名
currentItem = 0  # 当前的项目数,只是为了记录反馈用
params = []

拼接得到所有昵称类型

这里说下find和find_all的区别,find指找到满足条件的第一条,而find_all是找到满足条件的所有条目。

举个栗子:

params = [ "<a class='A'>item1</a>", "<a class='A'>item2</a>", "<a>item3</a>" ]

都使用(name='a', attrs={'class': 'A'})查找的情况下

find相当于找到 "<a class='A'>item1</a>"
find_all相当于找到 ["<a class='A'>item1</a>", "<a class='A'>item2</a>"]

然后,上代码

# 找到所有的子项目
for name in soup.find_all(name='a'):  # 拿到所有 a 标签
    child_path = name.get('href')  # 拿到 href 属性里的内容
    method = re.compile(r'/(.*?)/')  # 定义正则方法,将不需要的地址筛选掉
    flag = re.findall(method, child_path)  # 正则删选之后的结果
    if len(flag) == 1:  # 规定长度为了防止角标越界的状况发生
        if flag[0]:
            params.append(child_path)

分析状况

目前而言,知道的有几个信息

  1. 昵称有五十多种类型
  2. 每一种昵称都有多个页面
  3. 每一页都有n条数据

从这里开始逐个击破,我们需要先遍历出每一种类型的每一页里的每一条数据

这里我们先从类型入手

拿到每种类型的页数

其实这里我完全可以少写个循环的...咳

# 昵称类型
for i in range(len(params)):
    index = 411  # 这个数字还是必要的 你可以看看 http://www.oicq88.com/shanggan/97.htm 和 http://www.oicq88.com/shanggan/411.htm 之间是否有任何区别
    soup = BeautifulSoup(get_html(base_url + params[i] + '%s.htm' % index), 'lxml')  # 用一个巨大的数字,拿到一共有多少个页面

    item = soup.find_all(name='a', attrs={'class': 'hover', 'target': '_self'})  # 这个就是用来拿到最后一页的内容的方法

    index = int(item[0].text)  # 将最后一页的内容强转为int类型的数据,方便遍历每一页

    # 后面这两个就是给个反馈,用户体验更加爽快
    print('Current module is => ' + params[i] + 'And ...')
    print('\nThe page count is => ' + str(index) + '\n')

遍历每一页,并将数据写入文件中

这里可以说下,soup.find之后的值被for in遍历出来的,就不是BeautifulSoup对象了,所以不能用同样的方式去查看子类内容了

不过子类内容可以直接再次被遍历(这个坑卡的时间有点久,而且写博客的时候我发现这个问题能有n种方式解决...)

# 分页!
    for page in range(1, index):

        print('\ncurrent page => ' + str(page) + '\n')

        # 昵称项目 / 页
        soup = BeautifulSoup(get_html(base_url + params[i] + str(page) + '.htm'), 'lxml') 
        try:
            for ul in soup.find(name='ul', attrs={'class': 'list'}):
                for ls in ul:
                    for p in ls:
                        # 写入文件
                        try:
                            with codecs.open(file_name, "a", 'utf-8') as f:  # 这个用的是 a 方法,意味着直接在文件后面补充,不删除之前内容(重写),如果用w的话,可能会让字符串超出范围,从而抛出异常
                                f.write(p)
                                f.write('\n')
                                currentItem += 1
                                print('The ' + str(currentItem) + ' => Done')
                        except IOError:
                            print(IOError)
                        finally:
                            f.close()
        except ValueError:
            print('The null item not can be iterable...')

到此,差不多就这样了,简单记录下,代码直接copy就可用,前提是环境装齐

对于python,还是要经常抛出下错误,免得各种问题阻断了进程...不然爬一半炸了,时间都白费了

  1. 最好让爬取的内容更可控
  2. 有一套日志系统记录抛出的错误(现在想想把except里的内容改成文件流操作就行了)
  3. 好像也没什么了
posted @ 2019-07-05 19:57  Astroline  阅读(894)  评论(0编辑  收藏  举报