python数据抓取

  • 一、页面分析
  • 二、网页抓取方法
  • 1、正则表达式方法
  • 2、BeautifulSoup 模块
  • 3、lxml 模块
  • 4、各方法的对比总结
  • 三、Xpath选择器
  • 四、CSS选择器
  • 五、数据抓取总结
  • 六、性能测试源码


一、页面分析

  所谓的分析网页,就是理解一个网页的结构如何,了解需要字段的位置和形式。方便后期提取。了解页面最好的方法就是查看源代码。在大多数浏览器中,都可以使用开发者工具或者直接按F12,获取网页的源代码,下面以之前的文章做个示例:

Python如何抓取HTML网页 python网页抓取数据_Python如何抓取HTML网页


  上面例子中,很容易找到我们想要的阅读数和收藏数,只有这样分析清楚了,你才在后面通过class="read-count"和class="get-collection"获得想要的数据。当然,实际中的数据肯定是相对复杂的,不像例子这个简单。

二、网页抓取方法

  现在我们已经了解了该网页的结构,下面将会介绍 3 种抓取其中数据的方法。首先是正则表达式,然后是流行的 BeautifulSoup 模块,最后是强大的 lxml 模块。

1、正则表达式方法

  在通过正则表达式进行示例之前,假设你已经知道并且会使用python的正则表达式进行简单的提取,当然,你不会也没关系,请查看笔者之前的文章python的正则表达式进行学习。
  好嘛,相信你又温习了一下正则的使用,下面通过一个例子了解正则表达式在数据抓取时的方法。

例子:抓取文章的阅读数

url = 'https://blog.c n.net/Itsme_MrJJ/article/details/124836797'
headers = {'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/101.0.4951.54 Safari/537.36'}
res = requests.get(url, headers=headers)

pattern = re.compile(r'class="read-count">(.*?)<')
read_count = re.findall(pattern,res.text)
read_count

['60']

  可以看到,我们需要的数据已经获取到了,但是需要说明已下几个问题;

  • 上例使用的时findall,其他也可以获得结果,但实际中的抓取数据不止一个,所以都用findall
  • 实际中,网页页面会变化,导致数据抓取不到,需要重新修改正则匹配规则
  • 正则找规律相对复杂,尤其是页面复杂、提取的数据很多时

2、BeautifulSoup 模块

  Beautiful Soup 是一个非常流行的 Python 库,它可以解析网页,并提供了定位内容的便捷接口。当然,如果你还不知道,那么请看笔者之前的文章BeautifulSoup解析网页,当然也可以直接查看其官方文档。

  好的,假设你已经安装、了解BeautifulSoup 模块了,并通过里面的讲解,知道了大概的html的结构释义,那么请接着看下面通过BeautifulSoup 模块获取阅读数的例子。

url = 'https://blog.c n.net/Itsme_MrJJ/article/details/124836797'
headers = {'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/101.0.4951.54 Safari/537.36'}
res = requests.get(url, headers=headers)

soup = BeautifulSoup(res.text,features="lxml")
soup.find_all(class_='read-count')[0].string

'60'

  同样的,我们通过上面的例子也得到了想要的数据,而且比之前的正则表达式简洁了很多且抗干扰能力(前端编写时乱用的空格等)强了很多。但它也有一些需要注意的地方。

  • 该模块可以使用多个解析内核,之间又些许差异,使用时需要注意
  • soup的定位又多种,也时根据哪种得心应手用哪种,其中遍历树最麻烦,且往往得不到想要的
  • 改模块会补充、规范不规整的前端页面

3、lxml 模块

  Lxml 是基于 libxml2 这一 XML 解析库构建的 Python 库,它使用 C 语言编写,解析速度比 Beautiful Soup 更快,不过安装过程也更为复杂,尤其是在 Windows中。若是安装未成功,可以使用 Anaconda 。
  当然,也许你可能对 Anaconda 不太熟悉,它是由 Continuum Analytics 公司员工创建的主要专注于开源数据科学包的包和环境管理器。可以直接去其官网下载安装。
  和 Beautiful Soup 一样,使用 lxml 模块的第一步也是将有可能不合法的HTML 解析为统一格式。然后接着才是需求数据的抓取,对于数据的抓取,lxml 有几种不同的方法,比如 XPath 选择器和类似 Beautiful Soup 的 find()方法。但这里我们使用CSS 选择器。

from lxml.html import fromstring, tostring
tree = fromstring(res.text) # parse the HTML 
td = tree.cssselect('.read-count')[0]
td.text

'60'

  由于 cssselect 返回的是一个列表,我们需要获取其中的第一个结果,并调用 text方法,以迭代所有子元素并返回每个元素的相关文本。在本例中,尽管我们只有一个元素,但是该功能对于更加复杂的抽取示例来说非常有用。

4、各方法的对比总结

  关于每种抓取方法的优缺点。如下图:

Python如何抓取HTML网页 python网页抓取数据_python_02


  如果对你来说速度不是问题,并且更希望只使用 pip 安装库的话,那么使用较慢的方法(如 Beautiful Soup)也不成问题,但其实也没那么慢。如果只需抓取少量数据,并且想要避免额外依赖的话,那么正则表达式可能更加适合。不过,通常情况下,lxml 是抓取数据的最佳选择,这是因为该方法既快速又健壮,而正则表达式和 Beautiful Soup 或是速度不快,或是修改不易。

三、Xpath选择器

  原本写到这里是要结束的,但是考虑涉及到的CSS选择器和Xpath选择器知识没涉及到,且我之前也没写相关的东西,所以,作为补充的知识点再写点。在开始之前,先看看常用的页面属性。

属性

示例

说明

id

id=“MathJax_Message”

id 属性在 HTML 文档中必须是唯一的,这类似于公民的身份证号,具有很强的唯一性

name

name=“keywords”

name 来指定元素的名称,像是人的姓名,不唯一

class

class=“read-count”

class 来指定元素的类名。其用法与 id、name 类似

tag

<div>

类似<div>、<input>、<a>等 ,一样的标签会很多,所以通过标签定位需要注意

link

<link>

link 定位与前面介绍的几种定位方法有所不同,它专门用来定位文本链接

  XPath 是一种在 XML 文档中定位元素的语言。因为 HTML 可以看作 XML 的一种实现,所以我们可以通过Xpath定位抓取数据。
  相比BeautifulSoup,xpath所需要的依赖更少,只需要lxml这个解析器就可。并且xpath也可以在java,c++等多种语言中使用,使得爬虫不再局限于python语言。而Xpath处理的对象是一个etree对象,该对象由lxml生成。有两种生成方式:

# 本地文件
from lxml import etree
# 实例化好了一个etree对象,且将被解析的源码加载到了该对象中
tree = etree.parse('alice.html')

# 网络文件
import requests
from lxml import etree
url = 'https://blog.c n.net/Itsme_MrJJ/article/details/124836797'
headers = {'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/101.0.4951.54 Safari/537.36'}
res = requests.get(url, headers=headers)

# 实例化好了一个etree对象,且将被解析的源码加载到了该对象中
tree = etree.HTML(res.text)

  生成之后就是提取,xpath的常用规则及基本方法如下:

表达式

描述

nodename

选取此节点的所有子节点。

/

从根节点(标签)选取且必须从根节点开始

//

从匹配选择的当前节点选择文档中的节点,而不考虑它们的位置。

.

选取当前节点。

..

选取当前节点的父节点。

@

选取属性。

from lxml import etree
import requests
url = 'https://blog.c n.net/Itsme_MrJJ/article/details/124836797'
headers = {'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/101.0.4951.54 Safari/537.36'}
res = requests.get(url, headers=headers)

tree = etree.HTML(res.text)
tree.xpath('//span[@class="read-count"]/text()')

['61']

说明:
//span[@class=“read-count”]:从class属性等于read-count的span节点开始的所有text
因为使用的是//,所以不必指出前面的层级,这也是Xpath的强大之处,不需要一层层的找

四、CSS选择器

  CSS(Cascading Style Sheets)是一种语言,它用来描述 HTML 和 XML 文档的表现。CSS 使用选择器来为页面元素绑定属性。
  CSS 可以较为灵活地选择控件的任意属性,一般情况下定位速度要比 XPath 快,但对于初学者来说学习起来稍微有点难度,我们这里只介绍 CSS 在BeautifulSoup中的语法与使用。

  使用CSS选择器,只需要调用select()方法,并结合CSS选择器语法就可以定位元素的位置。具体语法规则如下表:

类型

表示

示例

说明

id选择器

#

soup.select(‘#content_views’)

选择id值为content_views的所有内容

类选择器

.

soup.select(‘.read-count’)

选择class值为read-count的所有内容

标签选择器

tag

soup.select(‘h1’)

选择所有h1标签的内容

混合选择器

综上

soup.select(‘span.read-count’)

选择class值为read-count的所有span标签数据

  我们以已经了解了Xpath和CSS选择器,那两者关于定位的区别如下:

Python如何抓取HTML网页 python网页抓取数据_Python如何抓取HTML网页_03

五、数据抓取总结

  写了这么多,总感觉有些乱,做一个图来整理思绪。

Python如何抓取HTML网页 python网页抓取数据_正则表达式_04

六、性能测试源码

import re
import requests
from bs4 import BeautifulSoup
import lxml.html
import time

#
#
#
#获取网页内容

def download(url,user_agent='wswp',proxy=None,num_retries=2):
    print ('Downloading:',url)
    headers = {'User-Agent': user_agent} 
    try: 
        resp = requests.get(url, headers=headers, proxies=proxy) 
        html = resp.text
        if resp.status_code >= 400: 
            print('Download error:', resp.text) 
            html = None 
            if num_retries and 500 <= resp.status_code < 600: 
                # recursively retry 5xx HTTP errors 
                return download(url, num_retries - 1)
    
    except requests.exceptions.RequestException as e: 
        
        print('Download error:', e.reason) 
        html = None
    return html

#使用正则表达式匹配
def re_scraper(html):
    results={}
    results['area']=re.search('<tr id="places_area__row">.*?<td class="w2p_fw">(.*?)</td>',html).groups()[0]
    return results


#使用BeautifulSoup匹配
def bs_scraper(html):
    soup=BeautifulSoup(html)
    results={}
    results['area']=soup.find('table').find('tr',id='places_area__row').find('td', class_='w2p_fw').string

    return results

#使用cssselect选择器匹配
def lxml_scraper(html):
    tree=lxml.html.fromstring(html)
    results={}
    conf=tree.cssselect('table > tr#places_area__row > td.w2p_fw')[0].text_content()
    results['area']=conf

    return results



#计算获取时间
#每个网站爬取的次数
NUM_ITERATIONS=1000
html=download('http://example.python-scraping.com/view/-1')
for name,scraper in [('Re',re_scraper),('Bs',bs_scraper),('Lxml',lxml_scraper)]:

    #开始的时间
    start=time.time()
    for i in range(NUM_ITERATIONS):
        if scraper==re_scraper:
#默认情况下,正则表达式模块会缓存搜索结果,为了使对比条件更一致,re.purge()方法清除缓存
            re.purge()
        result=scraper(html)
        #检查结果
        assert(result['area']=='647,500 square kilometres')

    #结束时间
    end=time.time()

    print('%s: %.2f seconds' %(name,end-start))