zoukankan      html  css  js  c++  java
  • 记录一次爬取某昵称网站的爬虫

    同学跑去实习了...然后工作的时候要她用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('
    The page count is => ' + str(index) + '
    ')
    

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

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

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

    # 分页!
        for page in range(1, index):
    
            print('
    current page => ' + str(page) + '
    ')
    
            # 昵称项目 / 页
            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('
    ')
                                    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. 好像也没什么了
  • 相关阅读:
    2019 ICPC Nanchang Summon
    2018 Multi-University Training Contest 10 B. Beads
    2020 ICPC Shanghai C
    2020 ICPC Shanghai B
    2020 ICPC Shanghai I
    2018 ICPC Shenyang G
    ACM-ICPC 2017 Asia Qingdao Suffix
    2018 Multi-University Training Contest 10 Calculate
    ACM-ICPC 2017 Asia Qingdao Our Journey of Xian Ends
    Java8-无限流补全日期,折线图补零
  • 原文地址:https://www.cnblogs.com/Arunoido/p/11140495.html
Copyright © 2011-2022 走看看