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. 好像也没什么了
  • 相关阅读:
    Palindrome Partitioning
    triangle
    Populating Next Right Pointers in Each Node(I and II)
    分苹果(网易)
    Flatten Binary Tree to Linked List
    Construct Binary Tree from Inorder and Postorder Traversal(根据中序遍历和后序遍历构建二叉树)
    iOS系统navigationBar背景色,文字颜色处理
    登录,注销
    ios 文字上下滚动效果Demo
    经常崩溃就是数组字典引起的
  • 原文地址:https://www.cnblogs.com/Arunoido/p/11140495.html
Copyright © 2011-2022 走看看