教老婆学python系列--手把手教你用Python3进行网络爬虫

运行的环境:

  1. win10 x64

  2. 安装了anaconda3,基于Python3环境运行

  3. 使用Pycharm编程

1. 前期工作

  • 安装 requests模块,API参考

  • 安装 BeautifulSoup 4.2模块,API参考

2. 主要目的

作为一个从事大数据小白,既然口口声声的说自己从事大数据,那么如果说自己不懂得怎么去收集数据,实在是有点说不过去。之前一直有过进行爬虫的相关经验,但是一直的没有对自己的知识进行一些整理,每次都是按照教程一步一步的设置,然后爬取数据。总的过程很是繁琐,也走了不少弯路。废话不多说😆。主要也是帮助我的「女朋友」完成论文所需要的文本素材。

直奔主题:本次主要是想收集小说网站,「武侠小说网」 http://www.wuxia.net.cn/author.html,中的关于武侠的所有文章。

网站的首页如下:

网站首页

我们使用chrome浏览器来分析其中的网络页面,“选中感兴趣的链接”→“右键检查”,弹出感兴趣的部分数据。

操作如图

使用谷歌浏览器,我们可以分析出我们需要爬取的主要两个步骤。

  1. 爬取「主页」中的所有作者链接,如第一张图片所示;

  2. 爬取作者页面下「所有文章」的文章链接;

  3. 爬取文章页面下的「所有文章章节」链接。

明确了思路之后,我们就依次实现这上面的实例。

3. 如何进行单个网页的爬取

我们明白了上面的思路,但我们该怎么一步一步的去实现,这个目的呢,就先就是如何利用Python进行单个网页的连接与解析。

如何使用request模块来获得网页的请求?这里有一个很重要的概念就是,在同一网站中,尽可能的使用同一个「session」。这样的目的可以很大节省我们“请求”→“服务器”之间请求的时间。具体代码操作如下。

session = requests.Session()  
session.get(startUrl)  # 设置回话  

查看request模块中,session.get方法的解释

解释图片

就是,我们使用_get()_方法之后,会获得一个对象,也会设定了一个会话session。这个session,可以继续用于我们在同一个网站内的访问。如果后面继续解析url的时候,没有使用同一个session (直接就是requests.get(url)),就会类似于认为每次重新打开浏览器,然后再输入链接,获取Response对象。而使用同一个session,就会认为是同一人在网站内直接内部跳转,这样可以加速解析url的速度。这也是每次我开始爬虫的时候,都会考虑设置一个「Session」对象。

1. 解析URL链接

url = "http://www.wuxia.net.cn/author.html"  
res = session.get(url)  # 通过session来获得Response对象  

如果你打印res的类型的话,就会发现是 <class 'requests.models.Response'>。返回Response对象,res就是描述整个url网页链接内容的描述结构。打印res.html属性,就会发现,和你直接在浏览器中查看源码所看到的内容是一样的。

2. 抛出链接解析的异常

当我们在链接大量的网页连接的时候,总是可能在执行的时候出现很多异常的情况,比如,链接超时,网页不存在等。导致网页异常,但是我们程序在运行的时候,通常都不会报告这种异常。我通常会在代码中加入 res.raise_for_status(),用来手动抛出异常。如果解析出现问题的话。

3. 正确的编码格式

不同网站的内容往往经过不同的编码,就算同一个网站,不同网页之间也存在使用不同编码的情况。如果不能够很好的处理网页编码的内容,那么很容易就得不到我们想要的结果。在解析的Response对象中,往往也会告诉我们该网站使用了何种编码格式。具体实现代码如下:

html = res.text  
# 需要重新定义下编码格式,不然会出现乱码,无法正确匹配数据  
html = html.encode(encoding=res.encoding, errors='ignore').decode(decodeType, errors='ignore')  

上面中,res.encoding是Response对象的res的属性,decodeType是自己指定的类型,我通常指定为“GBK”。

4. 使用合适的解析器来解析html文档

对于[上面](#3. 正确的编码格式) 得到的html,就是我们通常接触到的网页标准格式,有四种不同的解析器来解析html,它们在解析速度和方法上对后面即将介绍的查找方法,有一定程度的影响。就是不同解析器解释出来的对象,使用同样的,“选择器”,“过滤器”可能会得出不一样的结论。如果有兴趣查看4种不同解析器的影响,可以BeautifulSoup 官方文档 安装解析器。我们选用通用性、解析速度都较优的“lxml" 。

bsObj = bs4.BeautifulSoup(html, 'lxml')  

这样才真正的获得了一个BeautifulSoup对象。该对象,详细的解释了整个html的结构。

5. 如果获得自己感兴趣的那部分内容

上面4得到的bsObj对象,是完整的描述了整个网页内容,但是我们通常只需要获取其中我们感兴趣的一部分。这就要开始详细介绍BeautifulSoup中的「爬虫利器」,“选择器”select()方法。通过CSS的内容来选中自己需要的信息。

具体的可以参考官网教程:

用select()方法寻找元素,用法介绍 soup.select("选择器内容"),“选择器的内容”及可匹配的含义表示如下

  • div: 匹配所有名为 <div> 的元素

  • #author:匹配所有id属性为author的元素

  • .notice:匹配使用CSS中class属性名为notice的元素

  • div span 所有在<div>元素内的 <span> 元素

  • div > span :所有在<div>元素之内的<span>元素,中间没有其它的元素

  • input[name]:所有名为<input>,并有一个name属性,其值无所谓的元素

  • input[type="button":所有名为<input>,并有一个type属性,其值为button的元素

上面只是列举一些常用的CSS选择器的模式,其他的可以参考别的资料。返回的是一个tag对象列表

6. 小结

通过上面的分析,我们可以将它们包装组合到一起,让我们下次,只需要输入「url」和「解析规则」,就可以只返回我们感兴趣的内容。具体代码如下:

def parseFullUrl(url, rule, session=None, decodeType='gbk'):  
    """  
    根据url和规则解析返回的数据  
    :param url:  
    :param rule:  
    :param session:  
    :param decodeType:  
    :return:  
    """  
    try:  
        res = session.get(url)  
        res.raise_for_status()  
    except Exception as e:  
        logging.error("connection error: <{}> {}".format(url, e))  
        return None  
  
    html = res.text  
    # 需要重新定义下编码格式,不然会出现乱码,无法正确匹配数据  
    html = html.encode(encoding=res.encoding, errors='ignore').decode(decodeType, errors='ignore')  
    bsObj = bs4.BeautifulSoup(html, 'lxml')  
    return bsObj.select(rule)  

4. 获取武侠主页的所有作者链接

通过上面的分析,我们就应该清晰的知道,需要通过「主页链接」来获得所有的作者链接信息。我们需要两个重要的信息,一个就是主页链接,这个很容易获取,就是“http://www.wuxia.net.cn/author.html”;另一个就是「解析规则」。解析规则可以借助Chrome中的F12工具获取,具体的操作如下。

复制解析规则

选中,“检查”→“Copy”→“Copy Selector”,这个是时候可以看到,复制出来的内容为

#main > table > tbody > tr:nth-child(2) > td.tb > p:nth-child(1) > a  

语法需要参考CSS教程,这里不多说。但是如果你直接将这个复制到 bsObj.select(“”)中,你猜你会看到啥?

session = requests.Session()  
session.get(startUrl)  # 设置回话  
url = "http://www.wuxia.net.cn/author.html"  
rule = "#main > table > tbody > tr:nth-child(2) > td.tb > p:nth-child(1) > a"  
tags = parseFullUrl(url=url, rule=rule,session=session)  
print(tags)  

得到的结果,往往会是None。我个人也并不是很理解,很多情况我自己也是不断的在尝试,我个人认为可能是由于不同的解析器规则原因,因为解析的标准和实际CSS选择的标准不一样。如果你有更好的方法,欢迎留言告知。

所以这时候我通常会选择性的删除部分内容。将「解析规则」简化。比如,使用 rule = "tr > td.tb a"解析规则,就可以将所有的网页a标签解析出来。

1. 解释下 BeautifulSoup中的<a> 标签

<a>标签在网页爬取中,太常见啦,因为你通常都是从一个链接中获得一个链接再扩散到其它链接。如何获取 <a>标签中所需要的内容?具体的一个 <a>标签内容如下:

<a href="/author/baiyu.html" title="《偷拳》">白羽</a>  

我们通常需要获取 href中的链接,“/author/baiyu.html”,还有 tag里面的内容,“白羽”。其中 hreftitle<a>标签的属性,而“白羽” <a>标签的值。获取方式分别为:

tag.get("href") # 获取href属性值,如果不存在返回None  
tag.text # 获取标签内表示的内容  

不管是 <a>标签还是其它的html标签,获取属性和值得方式是一样的。

明白了这个,就可以批量的获取所有「作者页面」的所有「作者主页」链接。

2. 分析作者页面部分的源码

通过首页的部分源码,我们分析出,主要包含了两种我们感兴趣的内容,「authorUrl」和「作者名」,经过分析,主要有如两种内容格式:

<a href="/author/baiyu.html" title="《偷拳》">白羽</a>  
或者  
<a href="/author/bufeiyan.html" title="《武林客栈》《修罗道》《剑侠情缘》《九阙梦华》《华音流韶》"><strong>步非烟</strong></a>  

一种是作者在tag下的text内容中,一种是还包含了strong标签来修饰,对于这种我们需要分别处理。对于上面获取的链接,通过如下的代码,把所有的的「authorUrl」和「作者名」保存下来。

    lists = list()  # 用来存放所有的「作者信息」  
    for tag in tags:  
        dicts = {}  
        dicts["authorUrl"]=tag.get("href")  
        if tag.strong:  
            dicts["author"]=tag.strong.text  
        else:  
            dicts["author"] = tag.text  
        lists.append(dicts)

通常对于这类明显的包含了格式话的数据,后期可以考虑包装成一个类,不然每次序列化和反序列化还需要写出名称,很容易出错。

3. 保存整个list内容

序列化可以通过很多的方式,这个都可以参考很多序列化的内容,比如pickle,ppprint模块等,也可以参考廖雪峰的官网。但是我这里比较喜欢这届序列化为文本形式的json字符串形式,因为阅读性和可修改性非常的好。主要就是因为我自己并不是需要非常的注重性能。

这里我主要写下两个函数,分别用于方便的「读取」和「保存」我们得到的列表变量数据。

  1. 保存列表的变量
def dumpVariableToJson(variable, fileName, extName='.json'):  
    if not variable:  
        return  
    datetimeStr = datetime.now().strftime("%Y%m%d_%H_%M_%S")  
    fileName = fileName + datetimeStr + extName  
    with open(fileName, 'w', encoding='utf-8') as f:  
        for var in variable:  
            f.write(str(var) + "\n")  

  1. 读取列表中的变量值
def loadVariableFromJson(fileName, encoding='utf-8'):  
    result = []  
    if not os.path.exists(fileName) or not os.path.isfile(fileName):  
        logging.info("文件路径 <%s> 不存在或者不是文件名", fileName)  
        return result  
    with open(fileName, 'r', encoding=encoding) as f:  
        for line in f:  
            try:  
                tmpJson = eval(line)  
                result.append(tmpJson)  
            except Exception:  
                continue  
    return result  

我们在上面的程序中运行代码,将会在相对路径下生成如下文件名的文件。authors20180612_23_50_05.json

    # 保存整个列表变量  
    dumpVariableToJson(lists,"authors")

我这样费劲心机的序列化的主要原因就在于,方便后续的过程中能够直接从文件中读取数据变量。而不是每次都爬取一遍地址数据,同时可以在文本编辑器中直接的进行修改。因为「程序不是万能的」,也是偶尔可以直接通过文本修改。

加载文件,使用方法如下:

# 读取文件到内存中  
lists = loadVariableFromJson("authors20180612_23_50_05.json")  

4. 解决网页爬取过程中的相对url路径问题

很多时候我记得网上有一个叫urlparse的模块,专门处理这部分的逻辑,但是我这里就没有,直接使用自己写下的一个函数,简单粗暴的进行合并。

def mergeUrl(baseUrl, *kwargs):  
    """  
    组装出URL  
    :param baseUrl: 基础的Url,起始网站  
    :param kwargs: 各类相对路径网址  
    :return: 最终的绝对路径  
    """  
    items = baseUrl.split("/")  
    url = baseUrl.replace("/" + items[-1], "")  
    for subUrl in kwargs:  
        url += subUrl  
    return url  

5. 小结

下面是基于上面分析的内容,写出的第一次爬取所有的作者链接的代码。

    session = requests.Session()  
    startUrl = "http://www.wuxia.net.cn/author.html"  
    session.get(startUrl)  # 设置回话  
    # # 第一次爬取文章链接  
    rule = "tr > td.tb a"  
    tags = parseFullUrl(url=startUrl, rule=rule, session=session, decodeType="UTF-8")  
    lists = list()  # 用来存放所有的「作者信息」  
    for tag in tags:  
        dicts = {}  
        dicts["authorUrl"]=tag.get("href")  
        if tag.strong:  
            dicts["author"]=tag.strong.text  
        else:  
            dicts["author"] = tag.text  
        lists.append(dicts)  
    # 保存整个列表变量  
    dumpVariableToJson(lists,"authors")

5. 解析每个作者的文章链接

解析的主页内容。这里读取第二个作者的连接进行分析。

解析的主页内容

这里我们可以借鉴上面的思路,将获取的链接一样的存入文件。

整体之间的代码如下:

    # 第二次爬取所有的文章链接  
    titlesList = [] # 用来存储所有的文章链接  
    rule = "ul.co3 li a"  
    for authorDict in lists:  
        authorUrl = authorDict.get("authorUrl")  
        url = mergeUrl(startUrl,authorUrl)  
        tags = parseFullUrl(url=url, rule=rule, session=session, decodeType="UTF-8")  
        for tag in tags:  
            titleDicts = {}  
            titleDicts["authorUrl"] = url  
            titleDicts["author"] = authorDict.get("author")  
            titleDicts["titleUrl"]=tag.get("href")  
            if tag.strong:  
                titleDicts["titleName"]=tag.strong.text  
            else:  
                titleDicts["titleName"] = tag.text  
            titlesList.append(titleDicts)  
  
    # 保存所有「文章」链接  
    dumpVariableToJson(titlesList, "titles")

得到的json结果如下:

{'authorUrl': 'http://www.wuxia.net.cn/author/baiyu.html', 'author': '白羽', 'titleUrl': '/book/touquan.html', 'titleName': '偷拳'}  
{'authorUrl': 'http://www.wuxia.net.cn/author/bufeiyan.html', 'author': '步非烟', 'titleUrl': '/book/wulinkezhan.html', 'titleName': '武林客栈'}  
{'authorUrl': 'http://www.wuxia.net.cn/author/bufeiyan.html', 'author': '步非烟', 'titleUrl': '/book/xiuluodao.html', 'titleName': '修罗道'}  

6. 获取所有文章的章节链接

我们同样的选取所有内容。网站链接。分析网页的源码如下

<dl>  
    <dt>日曜卷·蛊神劫</dt>  
    <dd><a href="/book/wulinkezhan/1.html">第一章 剑门谁牵碧玉骢</a></dd>  
    <dd><a href="/book/wulinkezhan/2.html">第二章 身上衣衫寂寞红</a></dd>  
    <dd><a href="/book/wulinkezhan/3.html">第三章 振刀去国意气雄</a></dd>  
    <dd><a href="/book/wulinkezhan/4.html">第四章 置酒向君语从容</a></dd>  
    <dd><a href="/book/wulinkezhan/5.html">第五章 当时凄然一笑中</a></dd>  
    <dd><a href="/book/wulinkezhan/6.html">第六章 此日蹙兮五阵从</a></dd>  
    <dd><a href="/book/wulinkezhan/7.html">第七章 定许相思世世同</a></dd>  
    <dd><a href="/book/wulinkezhan/8.html">第八章 可怜心事画图空</a></dd>  
    <dd><a href="/book/wulinkezhan/9.html">第九章 身化秘魔驭毒龙</a></dd>  
    <dd><a href="/book/wulinkezhan/10.html">第十章 长怅秋山望飞鸿</a></dd>  
    <div class="clear"></div>  
</dl>  

根据文章的链接发现,文章的「文章名」在标签 dl dt下,「章节名」在标签 dl dd a标签中。

7. 获得所有的章节链接

    titleList = loadVariableFromJson("titles20180613_00_52_46.json")  
    # 第三次爬取所有的短文章节链接  
    rule = "div.book  dl"  
    chapterList = []  # 存放所有的章节链接  
    for titleDict in titleList:  
        titleUrl = titleDict.get("titleUrl")  
        authorUrl = titleDict.get("authorUrl")  
        author = titleDict.get("author")  
        titleName = titleDict.get("titleName")  
        url = mergeUrl(startUrl,titleUrl)  
        tags = parseFullUrl(url=url, rule=rule, session=session, decodeType="UTF-8")  
        for tag in tags:  
            chapterName = tag.dt.text if tag.dt else None  
            for ddVal in tag.select("dd a"):  
                chapterDicts = {}  
                chapterNameNum = ddVal.text  
                chapterNameUrl = ddVal.get("href")  
  
                # 增加  
                chapterDicts["author"] = author  
                chapterDicts["titleName"] = titleName  
                chapterDicts["titleUrl"] = titleUrl  
                chapterDicts["chapterName"] = chapterName  
                chapterDicts["chapterNameNum"] = chapterNameNum  
                chapterDicts["chapterNameUrl"] = chapterNameUrl  
                chapterList.append(chapterDicts)  
  
    # # 保存所有「文章章节」链接  
    dumpVariableToJson(chapterList, "chapters")

8. 解析每个文章链接中的内容

现在解析内容和前面的核心思想相差无几,就不过多的介绍啦,直接上代码。

    chapterList = loadVariableFromJson("chapters20180613_01_38_46.json")  
    for chapterDicts in chapterList:  
        author = chapterDicts["author"]  
        titleName = chapterDicts["titleName"]  
        chapterNameNum = chapterDicts["chapterNameNum"].replace(" ", "")  
        chapterNameUrl = chapterDicts["chapterNameUrl"]  
        url = mergeUrl(startUrl,chapterNameUrl)  
        context = getSinglePageTitileAndContext1(url,session)  
        parentPath = "./titles/" + nowDataStr + "/" + author  
        writeToFile(authorName=author,titleName=titleName + "_" + chapterNameNum,context=context,parentFilePath=parentPath)

9. 其它的一些相关介绍

常用的一些操作

  • 打开浏览器”
import webbrowser     
#webbrowser.open(strUrl)  

  • 使用程序模拟打开一个网页链接的操作

注意事项

  • 当编码格式出错是,可以使用 res.encoding = 'gbk' 来修改读入的编码格式
resp = requests.get(URL, params=params)  
  
resp.encoding = "gb2312"  
  
html = resp.text  
  
# html = html.encode(encoding="gbk", errors="ignore").decode("gbk", errors="ignore")  
  
html = html.encode(encoding="utf-8", errors="ignore").decode("utf-8", errors="ignore")  
  
print(html)  

过滤器

实际的爬虫过程中,常用的函数有findAll方法,该方法非常的好用,重点介绍。

先介绍一下过滤器的类,这些过滤器贯穿整个搜索的API.过滤器可以被用在tag的name中,节点的属性中,字符串中或他们的混合中.

  • 字符串:例如,查找文档中所有的<b>标签:soup.find_all('b')

  • 正则表达式:通过正则表达式的 match() 来匹配内容。下面例子中找出所有以b开头的标签,这表示<body><b>标签都应该被找到:

import re  
for tag in soup.find_all(re.compile("^b")):  
    print(tag.name)  

  • 列表:会返回列表中任一元素匹配的内容。找到文档中所有<a>标签和<b>标签:soup.find_all(["a", "b"])

  • True:True 可以匹配任何值,下面代码查找到所有的tag,但是不会返回字符串节点

for tag in soup.find_all(True):  
    print(tag.name)  

  • 方法: 方法只接受一个元素参数 ,如果这个方法返回 True 表示当前元素匹配并且被找到,如果不是则反回 False。下面方法校验了当前元素,如果包含 class 属性却不包含 id 属性,那么将返回 True。
def has_class_but_no_id(tag):  
    return tag.has_attr('class') and not tag.has_attr('id')  

  • 使用lambda表达式:也是方法的定义一种,唯一的限制条件必须把函数的标签作为参数且返回结果是布尔类型,例如,返回有两个属性的标签lambda tag: len(tag.attrs)==2
2. find_all()

非常好用的 find_all( name , attrs , recursive , text , **kwargs )。find_all() 方法搜索当前tag的所有tag子节点,并判断是否符合过滤器的条件.

Google API

  • 控制台页面 https://console.developers.google.com 开启和关闭API

  • 产品页面 https://developers.google.com/products

爬虫过程中编码问题

如果清楚的直到是那种编码格式,那就直接使用,如果不清楚,可以使用 from bs4 import UnicodeDammit来自动检测

html = res.text # 获取得到的文本  
html = html.encode(encoding=res.encoding, errors="ignore") # 转换为bytes  
dammit = bs4.UnicodeDammit(html) # 检测文本  
print(dammit.unicode_markup) # 显示unicode编码的格式  



### 后台运行Python3程序


nohup python3 -u scaptyTitle2.py > log.out 2>&1 &  

关于Python技术储备
Python越来越火了,离全民学Python的时代不远了,python应用场景那么多,不管是做主业还是副业或者别的都行,技多不压身,我这里有一份全套的 Python 学习资料,希望给那些想学习 Python 的小伙伴们一点帮助!

一、Python所有方向的学习路线
Python所有方向路线就是把Python常用的技术点做整理,形成各个领域的知识点汇总,它的用处就在于,你可以按照上面的知识点去找对应的学习资源,保证自己学得较为全面。
在这里插入图片描述

二、学习软件
工欲善其事必先利其器。学习Python常用的开发软件都在这里了,给大家节省了很多时间。
在这里插入图片描述

三、入门学习视频
我们在看视频学习的时候,不能光动眼动脑不动手,比较科学的学习方法是在理解之后运用它们,这时候练手项目就很适合了。
在这里插入图片描述

四、实战案例
光学理论是没用的,要学会跟着一起敲,要动手实操,才能将自己的所学运用到实际当中去,这时候可以搞点实战案例来学习。
在这里插入图片描述

五、面试资料
我们学习Python必然是为了找到高薪的工作,下面这些面试题是来自阿里、腾讯、字节等一线互联网大厂最新的面试资料,并且有阿里大佬给出了权威的解答,刷完这一套面试资料相信大家都能找到满意的工作。
在这里插入图片描述
在这里插入图片描述

这份完整版的Python全套学习资料已经上传CSDN,朋友们如果需要可以微信扫描下方CSDN官方认证二维码免费领取【保证100%免费
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值