Python 中 xpath 語法 與 lxml 庫解析 HTML/XML 和 CSS Selector

From:http://cuiqingcai.com/2621.htmlcss

The lxml.etree Tutorial :https://lxml.de/tutorial.htmlhtml

python3 解析 xml:https://www.cnblogs.com/deadwood-2016/p/8116863.htmljava

微軟文檔:  XPath 語法 和 XPath 函數
W3school Xpath 教程:http://www.w3school.com.cn/xpath/
Xpath 菜鳥教程:http://www.runoob.com/xpath/xpath-tutorial.html
簡書:Xpath高級用法:https://www.jianshu.com/p/1575db75670f
30個示例手把手教你學會Xpath高級用法:https://www.sohu.com/a/211716225_236714
瞭解XPath經常使用術語和表達式解析 十分鐘輕鬆入門:http://www.bazhuayu.com/blog/2014091node

 

 

前言

 

XPath 即爲 XML 路徑語言,它是一種用來肯定 XML(標準通用標記語言的子集)文檔中某部分位置的語言。
XPath 基於 XML 的樹狀結構,提供在數據結構樹中找尋節點的能力。 XPath 一樣也支持HTML。
XPath 是一門小型的查詢語言。
python 中 lxml庫 使用的是 Xpath 語法,是效率比較高的解析方法。python

lxml 用法源自 lxml python 官方文檔:http://lxml.de/index.html
XPath 語法參考 w3school:http://www.w3school.com.cn/xpath/index.aspgit

 

 

安裝

 

Python 安裝 lxml 庫web

pip3 install lxml

 

 

Python 中如何安裝使用 XPath

 

step1: 安裝 lxml 庫
step2: from lxml import etree                   
etree全稱:ElementTree 元素樹
step3: selector = etree.HTML(網頁源代碼)
step4: selector.xpath(一段神奇的符號)
express

 

lxml 使用 Xpath 使用示例:編程

#!/usr/bin/python3
# -*- coding: utf-8 -*-
# @Author      : 
# @File        : test.py
# @Software    : PyCharm
# @description : XXX


from lxml import etree


html = '''
<div id="content">   
   <ul id="useful">
      <li>有效信息1</li>
      <li>有效信息2</li>
      <li>有效信息3</li>
   </ul>
   <ul id="useless">
      <li>無效信息1</li>
      <li>無效信息2</li>
      <li>無效信息3</li>
   </ul>
</div>
<div id="url">
   <a href="http://cighao.com">陳浩的博客</a>
   <a href="http://cighao.com.photo" title="陳浩的相冊">點我打開</a>
</div>
'''


def test():
    selector = etree.HTML(html)

    # 提取 li 中的有效信息123
    content = selector.xpath('//ul[@id="useful"]/li/text()')
    for each in content:
        print(each)

    # 提取 a 中的屬性
    link = selector.xpath('//a/@href')
    for each in link:
        print(each)

    title = selector.xpath('//a/@title')
    for each in title:
        print(each)


if __name__ == '__main__':
    test()

lxml 使用 CSS 選擇器 使用 示例 1:json

#!/usr/bin/python3
# -*- coding: utf-8 -*-
# @Author      : 
# @File        : test_2.py
# @Software    : PyCharm
# @description : XXX


import lxml.html
from urllib.request import urlopen


def scrape(html):
    tree = lxml.html.fromstring(html)
    td = tree.cssselect('tr#places_neighbours__row > td.w2p_fw')[0]
    area = td.text_content()
    return area


if __name__ == '__main__':
    r_html = urlopen('http://example.webscraping.com/view/United-Kingdom-239').read()
    print(scrape(r_html))

lxml 使用 CSS 選擇器 使用 示例2 :

# -*- coding: utf-8 -*-

import csv
import re
import urlparse
import lxml.html
from link_crawler import link_crawler

FIELDS = ('area', 'population', 'iso', 'country', 'capital', 'continent', 
          'tld', 'currency_code', 'currency_name', 'phone', 'postal_code_format', 
          'postal_code_regex', 'languages', 'neighbours')


def scrape_callback(url, html):
    if re.search('/view/', url):
        tree = lxml.html.fromstring(html)
        row = [tree.cssselect('table > tr#places_{}__row > td.w2p_fw'.format(field))[0].text_content() for field in FIELDS]
        print url, row


if __name__ == '__main__':
    link_crawler('http://example.webscraping.com/', '/(index|view)', scrape_callback=scrape_callback)

link_crawler.py

import re
import urlparse
import urllib2
import time
from datetime import datetime
import robotparser
import Queue


def link_crawler(seed_url, link_regex=None, delay=5, max_depth=-1, max_urls=-1, headers=None, user_agent='wswp',
                 proxy=None, num_retries=1, scrape_callback=None):
    """Crawl from the given seed URL following links matched by link_regex
    """
    # the queue of URL's that still need to be crawled
    crawl_queue = [seed_url]
    # the URL's that have been seen and at what depth
    seen = {seed_url: 0}
    # track how many URL's have been downloaded
    num_urls = 0
    rp = get_robots(seed_url)
    throttle = Throttle(delay)
    headers = headers or {}
    if user_agent:
        headers['User-agent'] = user_agent

    while crawl_queue:
        url = crawl_queue.pop()
        depth = seen[url]
        # check url passes robots.txt restrictions
        if rp.can_fetch(user_agent, url):
            throttle.wait(url)
            html = download(url, headers, proxy=proxy, num_retries=num_retries)
            links = []
            if scrape_callback:
                links.extend(scrape_callback(url, html) or [])

            if depth != max_depth:
                # can still crawl further
                if link_regex:
                    # filter for links matching our regular expression
                    links.extend(link for link in get_links(html) if re.match(link_regex, link))

                for link in links:
                    link = normalize(seed_url, link)
                    # check whether already crawled this link
                    if link not in seen:
                        seen[link] = depth + 1
                        # check link is within same domain
                        if same_domain(seed_url, link):
                            # success! add this new link to queue
                            crawl_queue.append(link)

            # check whether have reached downloaded maximum
            num_urls += 1
            if num_urls == max_urls:
                break
        else:
            print
            'Blocked by robots.txt:', url


class Throttle:
    """Throttle downloading by sleeping between requests to same domain
    """

    def __init__(self, delay):
        # amount of delay between downloads for each domain
        self.delay = delay
        # timestamp of when a domain was last accessed
        self.domains = {}

    def wait(self, url):
        """Delay if have accessed this domain recently
        """
        domain = urlparse.urlsplit(url).netloc
        last_accessed = self.domains.get(domain)
        if self.delay > 0 and last_accessed is not None:
            sleep_secs = self.delay - (datetime.now() - last_accessed).seconds
            if sleep_secs > 0:
                time.sleep(sleep_secs)
        self.domains[domain] = datetime.now()


def download(url, headers, proxy, num_retries, data=None):
    print
    'Downloading:', url
    request = urllib2.Request(url, data, headers)
    opener = urllib2.build_opener()
    if proxy:
        proxy_params = {urlparse.urlparse(url).scheme: proxy}
        opener.add_handler(urllib2.ProxyHandler(proxy_params))
    try:
        response = opener.open(request)
        html = response.read()
        code = response.code
    except urllib2.URLError as e:
        print
        'Download error:', e.reason
        html = ''
        if hasattr(e, 'code'):
            code = e.code
            if num_retries > 0 and 500 <= code < 600:
                # retry 5XX HTTP errors
                html = download(url, headers, proxy, num_retries - 1, data)
        else:
            code = None
    return html


def normalize(seed_url, link):
    """Normalize this URL by removing hash and adding domain
    """
    link, _ = urlparse.urldefrag(link)  # remove hash to avoid duplicates
    return urlparse.urljoin(seed_url, link)


def same_domain(url1, url2):
    """Return True if both URL's belong to same domain
    """
    return urlparse.urlparse(url1).netloc == urlparse.urlparse(url2).netloc


def get_robots(url):
    """Initialize robots parser for this domain
    """
    rp = robotparser.RobotFileParser()
    rp.set_url(urlparse.urljoin(url, '/robots.txt'))
    rp.read()
    return rp


def get_links(html):
    """Return a list of links from html 
    """
    # a regular expression to extract all links from the webpage
    webpage_regex = re.compile('<a[^>]+href=["\'](.*?)["\']', re.IGNORECASE)
    # list of all links from the webpage
    return webpage_regex.findall(html)


if __name__ == '__main__':
    link_crawler(
        'http://example.webscraping.com', '/(index|view)', 
        delay=0, num_retries=1, user_agent='BadCrawler'
    )
    link_crawler(
        'http://example.webscraping.com', '/(index|view)', 
        delay=0, num_retries=1, max_depth=1, user_agent='GoodCrawler'
    )

 

 

 

Python3 解析 XML

 

來源:https://www.cnblogs.com/deadwood-2016/p/8116863.html

Python 使用 XPath 解析 XML文檔 :http://www.jingfengshuo.com/archives/1414.html

 

在 XML 解析方面,Python 貫徹了本身「開箱即用」(batteries included)的原則。在自帶的標準庫中,Python提供了大量能夠用於處理XML語言的包和工具,數量之多,甚至讓Python編程新手無從選擇。

本文將介紹深刻解讀利用Python語言解析XML文件的幾種方式,並以筆者推薦使用的ElementTree模塊爲例,演示具體使用方法和場景。

 

 

1、什麼是 XML?

XML是可擴展標記語言(Extensible Markup Language)的縮寫,其中的 標記(markup)是關鍵部分。您能夠建立內容,而後使用限定標記標記它,從而使每一個單詞、短語或塊成爲可識別、可分類的信息。

標記語言從早期的私有公司和政府制定形式逐漸演變成標準通用標記語言(Standard Generalized Markup Language,SGML)、超文本標記語言(Hypertext Markup Language,HTML),而且最終演變成 XML。XML有如下幾個特色。

  • XML的設計宗旨是傳輸數據,而非顯示數據。
  • XML標籤沒有被預約義。您須要自行定義標籤。
  • XML被設計爲具備自我描述性。
  • XML是W3C的推薦標準。

目前,XML在Web中起到的做用不會亞於一直做爲Web基石的HTML。 XML無所不在。XML是各類應用程序之間進行數據傳輸的最經常使用的工具,而且在信息存儲和描述領域變得愈來愈流行。所以,學會如何解析XML文件,對於Web開發來講是十分重要的。

 

2、有哪些能夠解析 XML 的 Python 包 ?

Python的標準庫中,提供了6種能夠用於處理XML的包。

xml.dom

xml.dom實現的是W3C制定的DOM API。若是你習慣於使用DOM API或者有人要求這這樣作,可使用這個包。不過要注意,在這個包中,還提供了幾個不一樣的模塊,各自的性能有所區別。

DOM解析器在任何處理開始以前,必須把基於XML文件生成的樹狀數據放在內存,因此DOM解析器的內存使用量徹底根據輸入資料的大小。

xml.dom.minidom

xml.dom.minidom是DOM API的極簡化實現,比完整版的DOM要簡單的多,並且這個包也小的多。那些不熟悉DOM的朋友,應該考慮使用xml.etree.ElementTree模塊。據lxml的做者評價,這個模塊使用起來並不方便,效率也不高,並且還容易出現問題。

xml.dom.pulldom

與其餘模塊不一樣,xml.dom.pulldom模塊提供的是一個「pull解析器」,其背後的基本概念指的是從XML流中pull事件,而後進行處理。雖然與SAX同樣採用事件驅動模型(event-driven processing model),可是不一樣的是,使用pull解析器時,使用者須要明確地從XML流中pull事件,並對這些事件遍歷處理,直處處理完成或者出現錯誤。

  • pull解析(pull parsing)是近來興起的一種XML處理趨勢。此前諸如SAX和DOM這些流行的XML解析框架,都是push-based,也就是說對解析工做的控制權,掌握在解析器的手中。

xml.sax

xml.sax模塊實現的是SAX API,這個模塊犧牲了便捷性來換取速度和內存佔用。SAX是Simple API for XML的縮寫,它並非由W3C官方所提出的標準。它是事件驅動的,並不須要一次性讀入整個文檔,而文檔的讀入過程也就是SAX的解析過程。所謂事件驅動,是指一種基於回調(callback)機制的程序運行方法。

xml.parser.expat

xml.parser.expat提供了對C語言編寫的expat解析器的一個直接的、底層API接口。expat接口與SAX相似,也是基於事件回調機制,可是這個接口並非標準化的,只適用於expat庫。

expat是一個面向流的解析器。您註冊的解析器回調(或handler)功能,而後開始搜索它的文檔。當解析器識別該文件的指定的位置,它會調用該部分相應的處理程序(若是您已經註冊的一個)。該文件被輸送到解析器,會被分割成多個片段,並分段裝到內存中。所以expat能夠解析那些巨大的文件。

xml.etree.ElementTree(如下簡稱ET)

xml.etree.ElementTree模塊提供了一個輕量級、Pythonic的API,同時還有一個高效的C語言實現,即xml.etree.cElementTree。與DOM相比,ET的速度更快,API使用更直接、方便。與SAX相比,ET.iterparse函數一樣提供了按需解析的功能,不會一次性在內存中讀入整個文檔。ET的性能與SAX模塊大體相仿,可是它的API更加高層次,用戶使用起來更加便捷。

筆者建議,在使用Python進行XML解析時,首選使用ET模塊,除非你有其餘特別的需求,可能須要另外的模塊來知足。

  • 解析XML的這幾種API並非Python首創的,Python也是經過借鑑其餘語言或者直接從其餘語言引入進來的。例如expat就是一個用C語言開發的、用來解析XML文檔的開發庫。而SAX最初是由DavidMegginson採用java語言開發的,DOM能夠以一種獨立於平臺和語言的方式訪問和修改一個文檔的內容和結構,能夠應用於任何編程語言。

下面,咱們以ElementTree模塊爲例,介紹在Python中如何解析lxml。

 

3、利用 ElementTree 解析 XML

Python標準庫中,提供了ET的兩種實現。一個是純Python實現的xml.etree.ElementTree,另外一個是速度更快的C語言實現xml.etree.cElementTree。請記住始終使用C語言實現,由於它的速度要快不少,並且內存消耗也要少不少。若是你所使用的Python版本中沒有cElementTree所需的加速模塊,你能夠這樣導入模塊:

try:
    import xml.etree.cElementTree as ET
except ImportError:
    import xml.etree.ElementTree as ET

若是某個API存在不一樣的實現,上面是常見的導入方式。固然,極可能你直接導入第一個模塊時,並不會出現問題。請注意,自Python 3.3以後,就不用採用上面的導入方法,由於ElemenTree模塊會自動優先使用C加速器,若是不存在C實現,則會使用Python實現。所以,使用Python 3.3+的朋友,只須要import xml.etree.ElementTree便可。

一、將XML文檔解析爲樹(tree)

咱們先從基礎講起。XML是一種結構化、層級化的數據格式,最適合體現XML的數據結構就是樹。ET提供了兩個對象:ElementTree將整個XML文檔轉化爲樹,Element則表明着樹上的單個節點。對整個XML文檔的交互(讀取,寫入,查找須要的元素),通常是在ElementTree層面進行的。對單個XML元素及其子元素,則是在Element層面進行的。下面咱們舉例介紹主要使用方法。

咱們使用下面的XML文檔,做爲演示數據:

<?xml version="1.0"?>
<doc>
  <branch name="codingpy.com" hash="1cdf045c">
    text,source
  </branch>
  <branch name="release01" hash="f200013e">
    <sub-branch name="subrelease01">
      xml,sgml
    </sub-branch>
  </branch>
  <branch name="invalid">
  </branch>
</doc>

接下來,咱們加載這個文檔,並進行解析:

>>> import xml.etree.ElementTree as ET
>>> tree = ET.ElementTree(file='doc1.xml')

而後,咱們獲取根元素(root element):

>>> tree.getroot()
<Element 'doc' at 0x11eb780>

正如以前所講的,根元素(root)是一個Element對象。咱們看看根元素都有哪些屬性:

>>> root = tree.getroot()
>>> root.tag, root.attrib
('doc', {})

沒錯,根元素並無屬性。與其餘Element對象同樣,根元素也具有遍歷其直接子元素的接口:

>>> for child_of_root in root:
...  print child_of_root.tag, child_of_root.attrib
...
branch {'hash': '1cdf045c', 'name': 'codingpy.com'}
branch {'hash': 'f200013e', 'name': 'release01'}
branch {'name': 'invalid'}

咱們還能夠經過索引值來訪問特定的子元素:

>>> root[0].tag, root[0].text
('branch', '\n    text,source\n  ')

二、查找須要的元素

從上面的示例中,能夠明顯發現咱們可以經過簡單的遞歸方法(對每個元素,遞歸式訪問其全部子元素)獲取樹中的全部元素。可是,因爲這是十分常見的工做,ET提供了一些簡便的實現方法。

Element對象有一個iter方法,能夠對某個元素對象之下全部的子元素進行深度優先遍歷(DFS)。ElementTree對象一樣也有這個方法。下面是查找XML文檔中全部元素的最簡單方法:

>>> for elem in tree.iter():
...  print elem.tag, elem.attrib
...
doc {}
branch {'hash': '1cdf045c', 'name': 'codingpy.com'}
branch {'hash': 'f200013e', 'name': 'release01'}
sub-branch {'name': 'subrelease01'}
branch {'name': 'invalid'}

在此基礎上,咱們能夠對樹進行任意遍歷——遍歷全部元素,查找出本身感興趣的屬性。可是ET可讓這個工做更加簡便、快捷。iter方法能夠接受tag名稱,而後遍歷全部具有所提供tag的元素:

>>> for elem in tree.iter(tag='branch'):
...  print elem.tag, elem.attrib
...
branch {'hash': '1cdf045c', 'name': 'codingpy.com'}
branch {'hash': 'f200013e', 'name': 'release01'}
branch {'name': 'invalid'}

三、支持經過 XPath 查找元素

使用XPath查找感興趣的元素,更加方便。Element對象中有一些find方法能夠接受Xpath路徑做爲參數,find方法會返回第一個匹配的子元素,findall以列表的形式返回全部匹配的子元素, iterfind則返回一個全部匹配元素的迭代器(iterator)。ElementTree對象也具有這些方法,相應地它的查找是從根節點開始的。

下面是一個使用XPath查找元素的示例:

>>> for elem in tree.iterfind('branch/sub-branch'):
...  print elem.tag, elem.attrib
...
sub-branch {'name': 'subrelease01'}

上面的代碼返回了branch元素之下全部tag爲sub-branch的元素。接下來查找全部具有某個name屬性的branch元素:

>>> for elem in tree.iterfind('branch[@name="release01"]'):
...  print elem.tag, elem.attrib
...
branch {'hash': 'f200013e', 'name': 'release01'}

四、構建 XML 文檔

利用ET,很容易就能夠完成XML文檔構建,並寫入保存爲文件。ElementTree對象的write方法就能夠實現這個需求。

通常來講,有兩種主要使用場景。一是你先讀取一個XML文檔,進行修改,而後再將修改寫入文檔,二是從頭建立一個新XML文檔。

修改文檔的話,能夠經過調整Element對象來實現。請看下面的例子:

>>> root = tree.getroot()
>>> del root[2]
>>> root[0].set('foo', 'bar')
>>> for subelem in root:
...  print subelem.tag, subelem.attrib
...
branch {'foo': 'bar', 'hash': '1cdf045c', 'name': 'codingpy.com'}
branch {'hash': 'f200013e', 'name': 'release01'}

在上面的代碼中,咱們刪除了root元素的第三個子元素,爲第一個子元素增長了新屬性。這個樹能夠從新寫入至文件中。最終的XML文檔應該是下面這樣的:

>>> import sys
>>> tree.write(sys.stdout)
<doc>
  <branch foo="bar" hash="1cdf045c" name="codingpy.com">
    text,source
  </branch>
  <branch hash="f200013e" name="release01">
    <sub-branch name="subrelease01">
      xml,sgml
    </sub-branch>
  </branch>
  </doc>

請注意,文檔中元素的屬性順序與原文檔不一樣。這是由於ET是以字典的形式保存屬性的,而字典是一個無序的數據結構。固然,XML也不關注屬性的順序。

從頭構建一個完整的文檔也很容易。ET模塊提供了一個SubElement工廠函數,讓建立元素的過程變得很簡單:

>>> a = ET.Element('elem')
>>> c = ET.SubElement(a, 'child1')
>>> c.text = "some text"
>>> d = ET.SubElement(a, 'child2')
>>> b = ET.Element('elem_b')
>>> root = ET.Element('root')
>>> root.extend((a, b))
>>> tree = ET.ElementTree(root)
>>> tree.write(sys.stdout)
<root><elem><child1>some text</child1><child2 /></elem><elem_b /></root>

五、利用 iterparse 解析XML流

XML文檔一般都會比較大,如何直接將文檔讀入內存的話,那麼進行解析時就會出現問題。這也就是爲何不建議使用DOM,而是SAX API的理由之一。

咱們上面談到,ET能夠將XML文檔加載爲保存在內存裏的樹(in-memory tree),而後再進行處理。可是在解析大文件時,這應該也會出現和DOM同樣的內存消耗大的問題吧?沒錯,的確有這個問題。爲了解決這個問題,ET提供了一個相似SAX的特殊工具——iterparse,能夠循序地解析XML。

接下來,筆者爲你們展現如何使用iterparse,並與標準的樹解析方式進行對比。咱們使用一個自動生成的XML文檔,下面是該文檔的開頭部分:

<?xml version="1.0" standalone="yes"?>
<site>
 <regions>
  <africa>
   <item id="item0">
    <location>United States</location>  <!-- Counting locations -->
    <quantity>1</quantity>
    <name>duteous nine eighteen </name>
    <payment>Creditcard</payment>
    <description>
     <parlist>
[...]

咱們來統計一下文檔中出現了多少個文本值爲Zimbabwe的location元素。下面是使用ET.parse的標準方法:

tree = ET.parse(sys.argv[2])
 
count = 0
for elem in tree.iter(tag='location'):
  if elem.text == 'Zimbabwe':
    count += 1
 
print count

上面的代碼會將所有元素載入內存,逐一解析。當解析一個約100MB的XML文檔時,運行上面腳本的Python進程的內存使用峯值爲約560MB,總運行時間問2.9秒。

請注意,咱們其實不須要講整個樹加載到內存裏。只要檢測出文本爲相應值得location元素便可。其餘數據均可以廢棄。這時,咱們就能夠用上 iterparse 方法了:

count = 0
for event, elem in ET.iterparse(sys.argv[2]):
  if event == 'end':
    if elem.tag == 'location' and elem.text == 'Zimbabwe':
      count += 1
  elem.clear() # 將元素廢棄
 
print count

上面的 for 循環會遍歷 iterparse 事件,首先檢查事件是否爲 end,而後判斷元素的 tag 是否爲 location,以及其文本值是否符合目標值。另外,調用 elem.clear() 很是關鍵:由於 iterparse 仍然會生成一個樹,只是循序生成的而已。廢棄掉不須要的元素,就至關於廢棄了整個樹,釋放出系統分配的內存。

當利用上面這個腳本解析同一個文件時,內存使用峯值只有 7MB,運行時間爲 2.5 秒。速度提高的緣由,是咱們這裏只在樹被構建時,遍歷一次。而使用 parse 的標準方法是先完成整個樹的構建後,纔再次遍歷查找所須要的元素。

iterparse 的性能與 SAX 至關,可是其 API 卻更加有用:iterparse 會循序地構建樹;而利用 SAX 時,你還得本身完成樹的構建工做。

 

4、使用示例( lxml 中 etree ):

#!/usr/bin/python3
# -*- coding: utf-8 -*-
# @Author      : 
# @File        : test_1.py
# @Software    : PyCharm
# @description : XXX


"""
Element是 XML處理的核心類,
Element對象能夠直觀的理解爲 XML的節點,大部分 XML節點的處理都是圍繞該類進行的。
這部分包括三個內容:節點的操做、節點屬性的操做、節點內文本的操做。
"""

from lxml import etree
import lxml.html as HTML

# 1.建立element
root = etree.Element('root')
print(root, root.tag)

# 2.添加子節點
child1 = etree.SubElement(root, 'child1')
child2 = etree.SubElement(root, 'child2')

# 3.刪除子節點
# root.remove(child2)

# 4.刪除全部子節點
# root.clear()

# 5.以列表的方式操做子節點
print(len(root))
print(root.index(child1))  # 索引號
root.insert(0, etree.Element('child3'))  # 按位置插入
root.append(etree.Element('child4'))  # 尾部添加

# 6.獲取父節點
print(child1.getparent().tag)
# print root[0].getparent().tag   #用列表獲取子節點,再獲取父節點
'''以上都是節點操做'''

# 7.建立屬性
# root.set('hello', 'dahu')   #set(屬性名,屬性值)
# root.set('hi', 'qing')

# 8.獲取屬性
# print(root.get('hello'))    #get方法
# print root.keys(),root.values(),root.items()    #參考字典的操做
# print root.attrib           #直接拿到屬性存放的字典,節點的attrib,就是該節點的屬性
'''以上是屬性的操做'''

# 9.text和tail屬性
# root.text = 'Hello, World!'
# print root.text

# 10.test,tail 和 text 的結合
html = etree.Element('html')
html.text = 'html.text'
body = etree.SubElement(html, 'body')
body.text = 'wo ai ni'
child = etree.SubElement(body, 'child')
child.text = 'child.text'  # 通常狀況下,若是一個節點的text沒有內容,就只有</>符號,若是有內容,纔會<>,</>都有
child.tail = 'tails'       # tail是在標籤後面追加文本
print(etree.tostring(html))
# print(etree.tostring(html, method='text'))  # 只輸出text和tail這種文本文檔,輸出的內容連在一塊兒,不實用

# 11.Xpath方式
# print(html.xpath('string()'))   #這個和上面的方法同樣,只返回文本的text和tail
print(html.xpath('//text()'))  # 這個比較好,按各個文本值存放在列表裏面
tt = html.xpath('//text()')
print(tt[0].getparent().tag)  # 這個能夠,首先我能夠找到存放每一個節點的text的列表,而後我再根據text找相應的節點
# for i in tt:
#     print i,i.getparent().tag,'\t',

# 12.判斷文本類型
print(tt[0].is_text, tt[-1].is_tail)  # 判斷是普通text文本,仍是tail文本
'''以上都是文本的操做'''

# 13.字符串解析,fromstring方式
xml_data = '<html>html.text<body>wo ai ni<child>child.text</child>tails</body></html>'
root1 = etree.fromstring(xml_data)  # fromstring,字面意思,直接來源字符串
# print root1.tag
# print etree.tostring(root1)

# 14.xml方式
root2 = etree.XML(xml_data)  # 和fromstring基本同樣,
print(etree.tostring(root2))

# 15.文件類型解析
'''
- a file name/path
- a file object
- a file-like object
- a URL using the HTTP or FTP protocol
'''
tree = etree.parse('text.html')  # 文件解析成元素樹
root3 = tree.getroot()  # 獲取元素樹的根節點
print(etree.tostring(root3, pretty_print=True))

parser = etree.XMLParser(remove_blank_text=True)  # 去除xml文件裏的空行
root = etree.XML("<root>  <a/>   <b>  </b>     </root>", parser)
print(etree.tostring(root))

# 16.html方式
xml_data1 = '<root>data</root>'
root4 = etree.HTML(xml_data1)
print(etree.tostring(root4))  # HTML方法,若是沒有<html>和<body>標籤,會自動補上
# 注意,若是是須要補全的html格式:這樣處理哦
with open("quotes-1.html", 'r') as f:
    a = HTML.document_fromstring(f.read().decode("utf-8"))

for i in a.xpath('//div[@class="quote"]/span[@class="text"]/text()'):
    print(i)

# 17.輸出內容,輸出xml格式
print(etree.tostring(root))
print(etree.tostring(root, xml_declaration=True, pretty_print=True, encoding='utf-8'))  # 指定xml聲明和編碼
'''以上是文件IO操做'''

# 18.findall方法
root = etree.XML("<root><a x='123'>aText<b/><c/><b/></a></root>")
print(root.findall('a')[0].text)  # findall操做返回列表
print(root.find('.//a').text)  # find操做就至關與找到了這個元素節點,返回匹配到的第一個元素
print(root.find('a').text)
print([b.text for b in root.findall('.//a')])  # 配合列表解析,至關帥氣!
print(root.findall('.//a[@x]')[0].tag)  # 根據屬性查詢
'''以上是搜索和定位操做'''
print(etree.iselement(root))
print(root[0] is root[1].getprevious())  # 子節點之間的順序
print(root[1] is root[0].getnext())
'''其餘技能'''
# 遍歷元素數
root = etree.Element("root")
etree.SubElement(root, "child").text = "Child 1"
etree.SubElement(root, "child").text = "Child 2"
etree.SubElement(root, "another").text = "Child 3"
etree.SubElement(root[0], "childson").text = "son 1"
# for i in root.iter():   #深度遍歷
# for i in root.iter('child'):    #只迭代目標值
#     print i.tag,i.text
# print etree.tostring(root,pretty_print=True)

簡單的建立和遍歷

from lxml import etree


# 建立
root = etree.Element('root')
# 添加子元素,併爲子節點添加屬性
root.append(etree.Element('child',interesting='sss'))
# 另外一種添加子元素的方法
body = etree.SubElement(root,'body')
body.text = 'TEXT' # 設置值
body.set('class','dd') # 設置屬性
//
# 輸出整個節點
print(etree.tostring(root, encoding='UTF-8', pretty_print=True))
//
//
# 建立,添加子節點、文本、註釋
root = etree.Element('root')
etree.SubElement(root, 'child').text = 'Child 1'
etree.SubElement(root, 'child').text = 'Child 2'
etree.SubElement(root, 'child').text = 'Child 3'
root.append(etree.Entity('#234'))
root.append(etree.Comment('some comment'))  # 添加註釋
# 爲第三個節點添加一個br
br = etree.SubElement(root.getchildren()[2],'br')
br.tail = 'TAIL'
for element in root.iter(): # 也能夠指定只遍歷是Element的,root.iter(tag=etree.Element)
   if isinstance(element.tag, str):
        print('%s - %s' % (element.tag, element.text))
    else:
        print('SPECIAL: %s - %s' % (element, element.text))

對 HTML / XML 的解析

# 先導入相關模塊
from lxml import etree
from io import StringIO, BytesIO
# 對html具備修復標籤補全的功能
broken_html = '<html><head><title>test<body><h1 class="hh">page title</h3>'
parser = etree.HTMLParser()
tree = etree.parse(StringIO(broken_html), parser) # 或者直接使用 html = etree.HTML(broken_html)
print(etree.tostring(tree, pretty_print=True, method="html"))
#
#
#用xpath獲取h1
h1 = tree.xpath('//h1')  # 返回的是一個數組
# 獲取第一個的tag
print(h1[0].tag)
# 獲取第一個的class屬性
print(h1[0].get('class'))
# 獲取第一個的文本內容
print(h1[0].text)
# 獲取全部的屬性的key,value的列表
print(h1[0].keys(),h1[0].values())

 

雜項

python3.5 lxml用法
問題1:有一個XML文件,如何解析 
問題2:解析後,若是查找、定位某個標籤 
問題3:定位後如何操做標籤,好比訪問屬性、文本內容等 
開始以前,首先是導入模塊,該庫經常使用的XML處理功能都在lxml.etree中
導入模塊:from lxml import etree 


Element類

Element是XML處理的核心類,Element對象能夠直觀的理解爲XML的節點,大部分XML節點的處理都是圍繞該類進行的。
這部分包括三個內容:節點的操做、節點屬性的操做、節點內文本的操做。

節點操做

一、建立Element對象
        直接使用Element方法,參數即節點名稱。
        root = etree.Element(‘root’) 
        print(root) 

二、獲取節點名稱
        使用tag屬性,獲取節點的名稱。
        print(root.tag) 
        root 

三、輸出XML內容
        使用tostring方法輸出XML內容(後文還會有補充介紹),參數爲Element對象。
        print(etree.tostring(root)) 
        b’’ 
四、添加子節點
        使用SubElement方法建立子節點,第一個參數爲父節點(Element對象),第二個參數爲子節點名稱。
        child1 = etree.SubElement(root, ‘child1’) 
        child2 = etree.SubElement(root, ‘child2’) 
        child3 = etree.SubElement(root, ‘child3’) 
五、刪除子節點
        使用remove方法刪除指定節點,參數爲Element對象。clear方法清空全部節點。
        root.remove(child1) # 刪除指定子節點 
        print(etree.tostring(root)) 
        b’’ 
        root.clear() # 清除全部子節點 
        print(etree.tostring(root)) 
        b’’ 
六、以列表的方式操做子節點
        能夠將Element對象的子節點視爲列表進行各類操做:
        child = root[0] # 下標訪問 
        print(child.tag) 
        child1
        print(len(root)) # 子節點數量 
        3
        root.index(child2) # 獲取索引號 
        1
        for child in root: # 遍歷 
        … print(child.tag) 
        child1 
        child2 
        child3
        root.insert(0, etree.Element(‘child0’)) # 插入 
        start = root[:1] # 切片 
        end = root[-1:]
        print(start[0].tag) 
        child0 
        print(end[0].tag) 
        child3
        root.append( etree.Element(‘child4’) ) # 尾部添加 
        print(etree.tostring(root)) 
        b’’ 
        其實前面講到的刪除子節點的兩個方法remove和clear也和列表類似。

七、獲取父節點
        使用getparent方法能夠獲取父節點。
        print(child1.getparent().tag) 
        root 


屬性操做

屬性是以key-value的方式存儲的,就像字典同樣。

一、建立屬性

        能夠在建立Element對象時同步建立屬性,第二個參數即爲屬性名和屬性值:
        root = etree.Element(‘root’, interesting=’totally’) 
        print(etree.tostring(root)) 
        b’’ 
        也可使用set方法給已有的Element對象添加屬性,兩個參數分別爲屬性名和屬性值:

        root.set(‘hello’, ‘Huhu’) 
        print(etree.tostring(root)) 
        b’’
        
二、獲取屬性
        屬性是以key-value的方式存儲的,就像字典同樣。直接看例子

        get方法得到某一個屬性值
        print(root.get(‘interesting’)) 
        totally
        keys方法獲取全部的屬性名
        sorted(root.keys()) 
        [‘hello’, ‘interesting’]

        items方法獲取全部的鍵值對
        for name, value in sorted(root.items()): 
            … print(‘%s = %r’ % (name, value)) 
        hello = ‘Huhu’ 
        interesting = ‘totally’ 
        
        也能夠用attrib屬性一次拿到全部的屬性及屬性值存於字典中:
        attributes = root.attrib 
        print(attributes) 
        {‘interesting’: ‘totally’, ‘hello’: ‘Huhu’}

        attributes[‘good’] = ‘Bye’ # 字典的修改影響節點 
        print(root.get(‘good’)) 
        Bye 


文本操做

標籤及標籤的屬性操做介紹完了,最後就剩下標籤內的文本了。
可使用text和tail屬性、或XPath的方式來訪問文本內容。

一、text 和 tail 屬性

        通常狀況,能夠用Element的text屬性訪問標籤的文本。
        root = etree.Element(‘root’) 
        root.text = ‘Hello, World!’ 
        print(root.text) 
        Hello, World! 
        print(etree.tostring(root)) 
        b’Hello, World!’ 

        Element類提供了tail屬性支持單一標籤的文本獲取。
        html = etree.Element(‘html’) 
        body = etree.SubElement(html, ‘body’) 
        body.text = ‘Text’ 
        print(etree.tostring(html)) 
        b’Text’
        
        br = etree.SubElement(body, ‘br’) 
        print(etree.tostring(html)) 
        b’Text’
                
        tail僅在該標籤後面追加文本       
        br.tail = ‘Tail’ 
        print(etree.tostring(br)) 
        b’
        Tail’
        
        print(etree.tostring(html)) 
        b’Text
        Tail’
        
        tostring方法增長method參數,過濾單一標籤,輸出所有文本
                
        print(etree.tostring(html, method=’text’)) 
        b’TextTail’ 
        
二、XPath方式
        方式一:過濾單一標籤,返回文本
        print(html.xpath(‘string()’)) 
        TextTail
        
        方式二:返回列表,以單一標籤爲分隔
        
        print(html.xpath(‘//text()’)) 
        [‘Text’, ‘Tail’] 
        方法二得到的列表,每一個元素都會帶上它所屬節點及文本類型信息,以下:
        
        texts = html.xpath(‘//text()’))
        
        print(texts[0]) 
        Text


所屬節點
        parent = texts[0].getparent() 
        print(parent.tag) 
        body
        
        print(texts[1], texts[1].getparent().tag) 
        Tail br

文本類型:是普通文本仍是tail文本
        print(texts[0].is_text) 
        True 
        print(texts[1].is_text) 
        False 
        print(texts[1].is_tail) 
        True 


文件 解析 與 輸出
回答問題1。

這部分講述如何將XML文件解析爲Element對象,以及如何將Element對象輸出爲XML文件。

一、文件解析

        文件解析經常使用的有fromstring、XML 和 HTML 三個方法。接受的參數都是字符串。
        xml_data = ‘data’
        fromstring方法
        root1 = etree.fromstring(xml_data) 
        print(root1.tag) 
        root 
        print(etree.tostring(root1)) 
        b’data’

        XML方法,與fromstring方法基本同樣

        root2 = etree.XML(xml_data) 
        print(root2.tag) 
        root 
        print(etree.tostring(root2)) 
        b’data’


        HTML方法,若是沒有和標籤,會自動補上
        root3 = etree.HTML(xml_data) 
        print(root3.tag) 
        html 
        print(etree.tostring(root3)) 
        b’data’ 
        
二、輸出
        輸出其實就是前面一直在用的tostring方法了,這裏補充xml_declaration和encoding兩個參數,前者是XML聲明,後者是指定編碼。
        root = etree.XML(‘‘)
        print(etree.tostring(root)) 
        b’’


XML聲明
print(etree.tostring(root, xml_declaration=True)) 
b」


指定編碼

print(etree.tostring(root, encoding=’iso-8859-1’)) 
b」

查找第一個b標籤
print(root.find(‘b’)) 
None 
print(root.find(‘a’).tag) 
a

查找全部b標籤,返回Element對象組成的列表

[ b.tag for b in root.findall(‘.//b’) ] 
[‘b’, ‘b’]


根據屬性查詢
print(root.findall(‘.//a[@x]’)[0].tag) 

print(root.findall(‘.//a[@y]’)) 
[] 
 

 

 

XPath語法

 

XPath 是一門在 XML 文檔中查找信息的語言。XPath 可用來在 XML 文檔中對元素和屬性進行遍歷。XPath 是 W3C XSLT 標準的主要元素,而且 XQuery 和 XPointer 都構建於 XPath 表達之上。

在 XPath 中,有七種類型的節點:元素、屬性、文本、命名空間、處理指令、註釋以及文檔(根)節點。XML 文檔是被做爲節點樹來對待的。樹的根被稱爲文檔節點或者根節點。

根節點在xpath中能夠用 「//」 來啊表示

 

XPath 經常使用規則

表達式 描述
nodename 選取此節點的全部子節點
/ 從當前節點選取直接子節點
// 從當前節點選取子孫節點
. 選取當前節點
.. 選取當前節點的父節點
@ 選取屬性
* 通配符,選擇全部元素節點與元素名
@* 選取全部屬性
[@attrib] 選取具備給定屬性的全部元素
[@attrib='value'] 選取給定屬性具備給定值的全部元素
[tag] 選取全部具備指定元素的直接子節點
[tag='text'] 選取全部具備指定元素而且文本內容是text節點

 

讀取 文本 解析節點 etree 會修復 HTML 文本節點

#!/usr/bin/python3
# -*- coding: utf-8 -*-
# @Author      : 
# @File        : test.py
# @Software    : PyCharm
# @description : XXX


from lxml import etree

text = '''
<div>
    <ul>
         <li class="item-0"><a href="link1.html">第一個</a></li>
         <li class="item-1"><a href="link2.html">second item</a></li>
         <li class="item-0"><a href="link5.html">a屬性</a>
     </ul>
 </div>
'''

html = etree.HTML(text)  # 初始化生成一個XPath解析對象
result = etree.tostring(html, encoding='utf-8')  # 解析對象輸出代碼
print(type(html))
print(type(result))
print(result.decode('utf-8'))


'''
執行結果:
<class 'lxml.etree._Element'>
<class 'bytes'>
<html><body><div>
    <ul>
         <li class="item-0"><a href="link1.html">第一個</a></li>
         <li class="item-1"><a href="link2.html">second item</a></li>
         <li class="item-0"><a href="link5.html">a屬性</a>
     </li></ul>
 </div>
</body></html>
'''

讀取 HTML文件 進行解析

from lxml import etree

html = etree.parse('test.html', etree.HTMLParser())  # 指定解析器HTMLParser會根據文件修復HTML文件中缺失的如聲明信息
result = etree.tostring(html)  # 解析成字節
# result=etree.tostringlist(html) #解析成列表
print(type(html))
print(type(result))
print(result)

 

 

節點關係

 

(1)父(Parent)

每一個元素以及屬性都有一個父。在下面的例子中,book 元素是 title、author、year 以及 price 元素的父:

<book>
  <title>Harry Potter</title>
  <author>J K. Rowling</author>
  <year>2005</year>
  <price>29.99</price>
</book>

(2)子(Children)

元素節點可有零個、一個或多個子。在下面的例子中,title、author、year 以及 price 元素都是 book 元素的子:

<book>
  <title>Harry Potter</title>
  <author>J K. Rowling</author>
  <year>2005</year>
  <price>29.99</price>
</book>

(3)同胞(Sibling)

擁有相同的父的節點。在下面的例子中,title、author、year 以及 price 元素都是同胞:

<book>
  <title>Harry Potter</title>
  <author>J K. Rowling</author>
  <year>2005</year>
  <price>29.99</price>
</book>

(4)先輩(Ancestor)

某節點的父、父的父,等等。在下面的例子中,title 元素的先輩是 book 元素和 bookstore 元素:

<bookstore>

<book>
  <title>Harry Potter</title>
  <author>J K. Rowling</author>
  <year>2005</year>
  <price>29.99</price>
</book>

</bookstore>

(5)後代(Descendant)

某個節點的子,子的子,等等。在下面的例子中,bookstore 的後代是 book、title、author、year 以及 price 元素:

<bookstore>

<book>
  <title>Harry Potter</title>
  <author>J K. Rowling</author>
  <year>2005</year>
  <price>29.99</price>
</book>

</bookstore>

 

選取節點

 

XPath 使用路徑表達式在 XML 文檔中選取節點。節點是經過沿着路徑或者 step 來選取的。

 

下面列出了最有用的路徑表達式:

表達式 描述
nodename 選取此節點的全部子節點
/ 從當前節點選取直接子節點
// 從當前節點選取子孫節點
. 選取當前節點
.. 選取當前節點的父節點
@ 選取屬性
* 通配符,選擇全部元素節點與元素名
@* 選取全部屬性
[@attrib] 選取具備給定屬性的全部元素
[@attrib='value'] 選取給定屬性具備給定值的全部元素
[tag] 選取全部具備指定元素的直接子節點
[tag='text'] 選取全部具備指定元素而且文本內容是text節點

 

實例

在下面的表格中,咱們已列出了一些路徑表達式以及表達式的結果:

路徑表達式 結果
bookstore 選取 bookstore 元素的全部子節點。
/bookstore 選取根元素 bookstore。註釋:假如路徑起始於正斜槓( / ),則此路徑始終表明到某元素的絕對路徑!
bookstore/book 選取屬於 bookstore 的子元素的全部 book 元素。
//book 選取全部 book 子元素,而無論它們在文檔中的位置。
bookstore//book 選擇屬於 bookstore 元素的後代的全部 book 元素,而無論它們位於 bookstore 之下的什麼位置。
//@lang 選取名爲 lang 的全部屬性。

 

謂語(Predicates)

謂語用來查找某個特定的節點或者包含某個指定的值的節點。謂語被嵌在方括號中。

實例

在下面的表格中,咱們列出了帶有謂語的一些路徑表達式,以及表達式的結果:

路徑表達式 結果
/bookstore/book[1] 選取屬於 bookstore 子元素的第一個 book 元素。
/bookstore/book[last()] 選取屬於 bookstore 子元素的最後一個 book 元素。
/bookstore/book[last()-1] 選取屬於 bookstore 子元素的倒數第二個 book 元素。
/bookstore/book[position()<3] 選取最前面的兩個屬於 bookstore 元素的子元素的 book 元素。
//title[@lang] 選取全部擁有名爲 lang 的屬性的 title 元素。
//title[@lang=’eng’] 選取全部 title 元素,且這些元素擁有值爲 eng 的 lang 屬性。
/bookstore/book[price>35.00] 選取 bookstore 元素的全部 book 元素,且其中的 price 元素的值須大於 35.00。
/bookstore/book[price>35.00]/title 選取 bookstore 元素中的 book 元素的全部 title 元素,且其中的 price 元素的值須大於 35.00。

 

選取未知節點

XPath 通配符可用來選取未知的 XML 元素。

通配符 描述
* 匹配任何元素節點。
@* 匹配任何屬性節點。
node() 匹配任何類型的節點。

實例

在下面的表格中,咱們列出了一些路徑表達式,以及這些表達式的結果:

路徑表達式 結果
/bookstore/* 選取 bookstore 元素的全部子元素。
//* 選取文檔中的全部元素。
//title[@*] 選取全部帶有屬性的 title 元素。

 

選取若干路徑

經過在路徑表達式中使用「|」運算符,您能夠選取若干個路徑。

實例

在下面的表格中,咱們列出了一些路徑表達式,以及這些表達式的結果:

路徑表達式 結果
//book/title | //book/price 選取 book 元素的全部 title 和 price 元素。
//title | //price 選取文檔中的全部 title 和 price 元素。
/bookstore/book/title | //price 選取屬於 bookstore 元素的 book 元素的全部 title 元素,以及文檔中全部的 price 元素。

 

XPath 運算符

下面列出了可用在 XPath 表達式中的運算符:( 此表參考來源:http://www.w3school.com.cn/xpath/xpath_operators.asp

運算符 描述 實例 返回值
| 計算兩個節點集 //book | //cd 返回全部擁有 book 和 cd 元素的節點集
+ 加法 6 + 4 10
減法 6 – 4 2
* 乘法 6 * 4 24
div 除法 8 div 4 2
= 等於 price=9.80 若是 price 是 9.80,則返回 true。若是 price 是 9.90,則返回 false。
!= 不等於 price!=9.80 若是 price 是 9.90,則返回 true。若是 price 是 9.80,則返回 false。
< 小於 price<9.80 若是 price 是 9.00,則返回 true。若是 price 是 9.90,則返回 false。
<= 小於或等於 price<=9.80 若是 price 是 9.00,則返回 true。若是 price 是 9.90,則返回 false。
> 大於 price>9.80 若是 price 是 9.90,則返回 true。若是 price 是 9.80,則返回 false。
>= 大於或等於 price>=9.80 若是 price 是 9.90,則返回 true。若是 price 是 9.70,則返回 false。
or price=9.80 or price=9.70 若是 price 是 9.80,則返回 true。若是 price 是 9.50,則返回 false。
and price>9.00 and price<9.90 若是 price 是 9.80,則返回 true。若是 price 是 8.50,則返回 false。
mod 計算除法的餘數 5 mod 2 1

 

 

XPath 函數的高級使用示例:

1.使用 contains() 和 and
    //div[starts-with(@id,'res')]//table[1]//tr//td[2]//a//span[contains(.,'_Test') and contains(.,'KPI')] 
    //div[contains(@id,'in')] ,表示選擇id中包含有’in’的div節點
2.text():
    因爲一個節點的文本值不屬於屬性,好比「<a class=」baidu「 href=」http://www.baidu.com「>baidu</a>」,
    因此,用text()函數來匹配節點://a[text()='baidu']
    //span[@id='idHeaderTitleCell' and contains(text(),'QuickStart')]
3.last():
    前面已介紹
4. 使用starts-with()
    //div[starts-with(@id,'in')] ,表示選擇以’in’開頭的id屬性的div節點
    //div[starts-with(@id,'res')]//table//tr//td[2]//table//tr//td//a//span[contains(.,'Developer Tutorial')]
5.not()函數,表示否認。not()函數一般與返回值爲true or false的函數組合起來用,
    好比contains(),starts-with()等,但有一種特別狀況請注意一下:
    咱們要匹配出input節點含有id屬性的,寫法爲://input[@id],
    若是咱們要匹配出input節點不含用id屬性的,則爲://input[not(@id)]
    //input[@name=‘identity’ and not(contains(@class,‘a’))] ,表示匹配出name爲identity而且class的值中不包含a的input節點。
6.使用descendant
    //div[starts-with(@id,'res')]//table[1]//tr//td[2]//a//span[contains(.,'QuickStart')]/../../../descendant::img
7.使用ancestor
//div[starts-with(@id,'res')]//table[1]//tr//td[2]//a//span[contains(.,'QuickStart')]/ancestor::div[starts-with(@id,'res')]//table[2]//descendant::a[2]

示例代碼:

# -*- coding: utf-8 -*-
# @Author  :
# @File    : douban_api.py
# @Software: PyCharm
# @description : XXX


import re
import json
import datetime
import requests
from lxml import etree
import urllib3


urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)


class DBSpider(object):

    def __init__(self):
        self.custom_headers = {
            'Host': 'movie.douban.com',
            'Connection': 'keep-alive',
            'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 '
                          '(KHTML, like Gecko) Chrome/80.0.3987.87 Safari/537.36',
            'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,'
                      'image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9',
        },

        # self.proxies = {
        #     'http': '127.0.0.1:8080',
        #     'https': '127.0.0.1:8080'
        # }

        self.s = requests.session()
        self.s.verify = False
        self.s.headers = self.custom_headers
        # self.s.proxies = self.proxies

    def __del__(self):
        pass

    def api_artists_info(self, artists_id=None):
        ret_val = None
        if artists_id:
            url = f'https://movie.douban.com/celebrity/{artists_id}/'
            try:
                r = self.s.get(url)
                if 200 == r.status_code:
                    response = etree.HTML(r.text)

                    artists_name = response.xpath('//h1/text()')
                    artists_name = artists_name[0] if len(artists_name) else ''

                    chinese_name = re.findall(r'([\u4e00-\u9fa5·]+)', artists_name)
                    chinese_name = chinese_name[0] if len(chinese_name) else ''

                    english_name = artists_name.replace(chinese_name, '').strip()

                    pic = response.xpath('//div[@id="headline"]//div[@class="pic"]//img/@src')
                    pic = pic[0] if len(pic) else ''

                    sex = response.xpath('//div[@class="info"]//span[contains(text(), "性別")]/../text()')
                    sex = ''.join(sex).replace('\n', '').replace(':', '').strip() if len(sex) else ''

                    constellation = response.xpath('//div[@class="info"]//span[contains(text(), "星座")]/../text()')
                    constellation = ''.join(constellation).replace('\n', '').replace(':', '').strip() if len(constellation) else ''

                    birthday = response.xpath('//div[@class="info"]//span[contains(text(), "日期")]/../text()')
                    birthday = ''.join(birthday).replace('\n', '').replace(':', '').strip() if len(birthday) else ''

                    place = response.xpath('//div[@class="info"]//span[contains(text(), "出生地")]/../text()')
                    place = ''.join(place).replace('\n', '').replace(':', '').strip() if len(place) else ''

                    occupation = response.xpath('//div[@class="info"]//span[contains(text(), "職業")]/../text()')
                    occupation = ''.join(occupation).replace('\n', '').replace(':', '').strip() if len(occupation) else ''

                    desc = ''.join([x for x in response.xpath('//span[@class="all hidden"]/text()')])

                    artists_info = dict(
                        artistsId=artists_id,
                        homePage=f'https://movie.douban.com/celebrity/{artists_id}',
                        sex=sex,
                        constellation=constellation,
                        chineseName=chinese_name,
                        foreignName=english_name,
                        posterAddre=pic,
                        # posterAddreOSS=Images.imgages_data(pic, 'movie/douban'),
                        birthDate=birthday,
                        birthAddre=place,
                        desc=desc,
                        occupation=occupation,
                        showCount='',
                        fetchTime=str(datetime.datetime.now()),
                    )
                    # print(json.dumps(artists_info, ensure_ascii=False, indent=4))
                    ret_val = artists_info
                else:
                    print(f'status code : {r.status_code}')
            except BaseException as e:
                print(e)
        return ret_val

    def test(self):
        pass


if __name__ == '__main__':
    douban = DBSpider()
    # temp_uid = '1044707'
    # temp_uid = '1386515'
    # temp_uid = '1052358'
    temp_uid = '1052357'
    user_info = douban.api_artists_info(temp_uid)
    print(json.dumps(user_info, ensure_ascii=False, indent=4))
    pass

 

 

 

Xpath 高級用法

 

scrapy實戰2,使用內置的xpath,re 和 css 提取值:http://www.noobyard.com/article/p-bgywtsxh-h.html

span 標籤 class 屬性包含  selectable 字符串://span[contains(@class, 'selectable')]

匹配貓眼 座位數
        //div[@class='seats-wrapper']/div/span[contains(@class,'seat') and not(contains(@class,'empty'))]
    等價於
        //div[@class='seats-wrapper']/div//span[not(contains(//span[contains(@class, 'seat')]/@class, 'empty'))]


./@data-val
//div[contains(@class, "show-list") and @data-index="{0}"]
.//div[@class="show-date"]//span[contains(@class, "date-item")]/text()
.//div[contains(@class, "plist-container")][1]//tbody//tr     xpath 中下標是從 1 開始的
substring-before(substring-after(//script[contains(text(), '/apps/feedlist')]/text(), 'html":"'), '"})')
//div[text()="hello"]/p/text()
//a[@class="movie-name"][1]/text()
string(//a[@class="movie-name"][1])

 

1. 獲取父節點屬性
    首先選中 href 屬性爲 link4.html的a節點,而後再獲取其父節點,而後再獲取其class屬性
    result1 = response.xpath('//a[@href="link4.html"]/../@class')
    咱們也能夠經過parent::來獲取父節點
    result2 = response.xpath('//a[@href="link4.html"]/parent::*/@class')
    注意: //a表示html中的全部a節點,他們的href屬性有多個,這裏[]的做用是屬性匹配,找到a的href屬性爲link4.html的節點
2. 獲取節點內部文本
    獲取class爲item-1的li節點文本,
    result3 = response.xpath('//li[@class="item-0"]/a/text()')
    返回結果爲:['first item', 'fifth item']
3. 屬性獲取
    獲取全部li節點下的全部a節點的href屬性
    result4 = response.xpath('//li/a/@href')
    返回結果爲:['link1.html', 'link2.html', 'link3.html', 'link4.html', 'link5.html']
4. 按序選擇
    result = response.xpath('//li[1]/a/text()')   #選取第一個li節點
    result = response.xpath('//li[last()]/a/text()')   #選取最後一個li節點
    result = response.xpath('//li[position()<3]/a/text()')   #選取位置小於3的li節點,也就是1和2的節點
    result = response.xpath('//li[last()-2]/a/text()')  #選取倒數第三個節點 
5. 節點軸選擇
    1)返回第一個li節點的全部祖先節點,包括html,body,div和ul
                result = response.xpath('//li[1]/ancestor::*')     
    2)返回第一個li節點的<div>祖先節點
                result = response.xpath('//li[1]/ancestor::div')     
    3)返回第一個li節點的全部屬性值
                result = response.xpath('//li[1]/attribute::*')     
    4)首先返回第一個li節點的全部子節點,而後加上限定條件,選組href屬性爲link1.html的a節點
                result = response.xpath('//li[1]/child::a[@href="link1.html"]')     
    5)返回第一個li節點的全部子孫節點,而後加上只要span節點的條件
                result = response.xpath('//li[1]/descendant::span')     
    6)following軸可得到當前節點以後的全部節點,雖然使用了*匹配,可是又加了索引選擇,因此只獲取第2個後續節點,也就是第2個<li>節點中的<a>節點
                result = response.xpath('//li[1]/following::*[2]')     
    7)following-sibling可獲取當前節點以後的全部同級節點,也就是後面全部的<li>節點
                result = response.xpath('//li[1]/following-sibling::*') 
6. 屬性多值匹配
                <li class="li li-first"><a href="link.html">first item</a></li>     
                result5 = response.xpath('//li[@class="li"]/a/text()')
    返回值爲空,由於這裏HTML文本中li節點爲class屬性有2個值li和li-first,若是還用以前的屬性匹配就不行了,須要用contain()函數     
    正確方法以下
                result5 = response.xpath('//li[contains(@class, "li")]/a/text()')
    contains()方法中,第一個參數爲屬性名,第二個參數傳入屬性值,只要此屬性名包含所傳入的屬性值就可完成匹配 
 7. 多屬性匹配,這裏說一下不用框架的時候,xpath的常規用法
    有時候咱們須要多個屬性來肯定一個節點,那麼就須要同時匹配多個屬性,可用and來鏈接    
    from lxml import etree
    text = '''
    <li class = "li li-first" name="item"><a href="link.html">first item</a></li>
    '''
    html = etree.HTML(text)
    result6 = html.xpath('//li[contains(@class, "li") and @name="item"]/a/text()')
    print(result)    
    這裏的li節點有class和name兩個屬性,須要用and操做符相連,而後置於中括號內進行條件篩選

 

xpath 學習筆記

1.依靠本身的屬性,文本定位
   //td[text()='Data Import']
   //div[contains(@class,'cux-rightArrowIcon-on')]
   //a[text()='立刻註冊']
   //input[@type='radio' and @value='1']     多條件
   //span[@name='bruce'][text()='bruce1'][1]   多條件
   //span[@id='bruce1' or text()='bruce2']  找出多個
   //span[text()='bruce1' and text()='bruce2']  找出多個
2.依靠父節點定位
  //div[@class='x-grid-col-name x-grid-cell-inner']/div
  //div[@id='dynamicGridTestInstanceformclearuxformdiv']/div
  //div[@id='test']/input
3.依靠子節點定位
  //div[div[@id='navigation']]
  //div[div[@name='listType']]
  //div[p[@name='testname']]
4.混合型
  //div[div[@name='listType']]//img
  //td[a//font[contains(text(),'seleleium2從零開始 視屏')]]//input[@type='checkbox']
5.進階部分
   //input[@id='123']/following-sibling::input   找下一個兄弟節點
   //input[@id='123']/preceding-sibling::span    上一個兄弟節點
   //input[starts-with(@id,'123')]               以什麼開頭
   //span[not(contains(text(),'xpath'))]        不包含xpath字段的span
6.索引
  //div/input[2]
  //div[@id='position']/span[3]
  //div[@id='position']/span[position()=3]
  //div[@id='position']/span[position()>3]
  //div[@id='position']/span[position()<3]
  //div[@id='position']/span[last()]
  //div[@id='position']/span[last()-1]
7.substring 截取判斷
  <div data-for="result" id="swfEveryCookieWrap"></div>
  //*[substring(@id,4,5)='Every']/@id  截取該屬性 定位3,取長度5的字符 
  //*[substring(@id,4)='EveryCookieWrap']  截取該屬性從定位3 到最後的字符 
  //*[substring-before(@id,'C')='swfEvery']/@id   屬性 'C'以前的字符匹配
  //*[substring-after(@id,'C')='ookieWrap']/@id   屬性'C以後的字符匹配
8.通配符*
  //span[@*='bruce']
  //*[@name='bruce']
9.軸
  //div[span[text()='+++current node']]/parent::div    找父節點
  //div[span[text()='+++current node']]/ancestor::div    找祖先節點
10.孫子節點
  //div[span[text()='current note']]/descendant::div/span[text()='123']
  //div[span[text()='current note']]//div/span[text()='123']          兩個表達的意思同樣
11.following pre
https://www.baidu.com/s?wd=xpath
  //span[@class="fk fk_cur"]/../following::a       往下的全部a
  //span[@class="fk fk_cur"]/../preceding::a[1]    往上的全部a
  
xpath提取多個標籤下的text

在寫爬蟲的時候,常常會使用xpath進行數據的提取,對於以下的代碼:
<div id="test1">你們好!</div>
使用xpath提取是很是方便的。假設網頁的源代碼在selector中:
data = selector.xpath('//div[@id="test1"]/text()').extract()[0]
就能夠把「你們好!」提取到data變量中去。
然而若是遇到下面這段代碼呢?
<div id="test2">美女,<font color=red>你的微信是多少?</font><div>
若是使用:
data = selector.xpath('//div[@id="test2"]/text()').extract()[0]
只能提取到「美女,」;
若是使用:
data = selector.xpath('//div[@id="test2"]/font/text()').extract()[0]
又只能提取到「你的微信是多少?」
但是我本意是想把「美女,你的微信是多少?」這一整個句子提取出來。
<div id="test3">我左青龍,<span id="tiger">右白虎,
<ul>上朱雀,<li>下玄武。</li></ul>老牛在當中,</span>龍頭在胸口。
<div>
並且內部的標籤還不固定,若是我有一百段這樣相似的html代碼,
又如何使用xpath表達式,以最快最方便的方式提取出來?
使用xpath的string(.)
以第三段代碼爲例:
data = selector.xpath('//div[@id="test3"]')
info = data.xpath('string(.)').extract()[0]
這樣,就能夠把「我左青龍,右白虎,上朱雀,下玄武。老牛在當中,龍頭在胸口」整個句子提取出來,
賦值給info變量。

示例 XML 文檔

<?xml version="1.0" encoding="utf8"?>
<bookstore>
  <book>
    <title lang="eng">Harry Potter</title>
    <price>29.99</price>
  </book>
  <book>
    <title lang="eng">Learning XML</title>
    <price>39.95</price>
  </book>
</bookstore>

選取節點

如下爲基本路徑的表達方式,記住 XPath 的路徑表達式都是基於某個節點之上的,例如最初的當前節點通常是根節點,這與 Linux 下路徑切換原理是同樣的。

表達式 描述
nodename 選取已匹配節點下名爲 nodename 的子元素節點。
/ 若是以 / 開頭,表示從根節點做爲選取起點。
// 在已匹配節點後代中選取節點,不考慮目標節點的位置。
. 選取當前節點。
.. 選取當前節點的父元素節點。
@ 選取屬性。

 

>>> from lxml import etree
>>> xml = """<?xml version="1.0" encoding="utf8"?>
<bookstore>
  <book>
    <title lang="eng">Harry Potter</title>
    <price>29.99</price>
  </book>
  <book>
    <title lang="eng">Learning XML</title>
    <price>39.95</price>
  </book>
</bookstore>"""


# 獲得根節點
>>> root = etree.fromstring(xml)  
>>> print root
<Element bookstore at 0x2c9cc88>

# 選取全部book子元素
>>> root.xpath('book')  
[<Element book at 0x2d88878>, <Element book at 0x2d888c8>]

# 選取根節點bookstore
>>> root.xpath('/bookstore')  
[<Element bookstore at 0x2c9cc88>]

# 選取全部book子元素的title子元素
>>> root.xpath('book/title')  
[<Element title at 0x2d88878>, <Element title at 0x2d888c8>]

# 以根節點爲始祖,選取其後代中的title元素
>>> root.xpath('//title')    
[<Element title at 0x2d88878>, <Element title at 0x2d888c8>]

# 以book子元素爲始祖,選取後代中的price元素
>>> root.xpath('book//price')  
[<Element price at 0x2ca20a8>, <Element price at 0x2d88738>]

# 以根節點爲始祖,選取其後代中的lang屬性值
>>> root.xpath('//@lang')    
['eng', 'eng']

預判(Predicates)

預判是用來查找某個特定的節點或者符合某種條件的節點,預判表達式位於方括號中。

# 選取bookstore的第一個book子元素

>>> root.xpath('/bookstore/book[1]')          

[<Element book at 0x2ca20a8>]


# 選取bookstore的最後一個book子元素

>>> root.xpath('/bookstore/book[last()]')        

[<Element book at 0x2d88878>]


# 選取bookstore的倒數第二個book子元素

>>> root.xpath('/bookstore/book[last()-1]')      

[<Element book at 0x2ca20a8>]


# 選取bookstore的前兩個book子元素

>>> root.xpath('/bookstore/book[position()<3]')    

[<Element book at 0x2ca20a8>, <Element book at 0x2d88878>]


# 以根節點爲始祖,選取其後代中含有lang屬性的title元素

>>> root.xpath('//title[@lang]')     

[<Element title at 0x2d888c8>, <Element title at 0x2d88738>]


# 以根節點爲始祖,選取其後代中含有lang屬性而且值爲eng的title元素

>>> root.xpath("//title[@lang='eng']")

[<Element title at 0x2d888c8>, <Element title at 0x2d88738>]


# 選取bookstore子元素book,條件是book的price子元素要大於35

>>> root.xpath("/bookstore/book[price>35.00]")

[<Element book at 0x2ca20a8>]


# 選取bookstore子元素book的子元素title,條件是book的price子元素要大於35

>>> root.xpath("/bookstore/book[price>35.00]/title")

[<Element title at 0x2d888c8>]

通配符

通配符 描述
* 匹配任何元素。
@* 匹配任何屬性。
node() 匹配任何類型的節點。

 

 

 

 

# 選取 bookstore 全部子元素

>>> root.xpath('/bookstore/*')

[<Element book at 0x2d888c8>, <Element book at 0x2ca20a8>]


# 選取根節點的全部後代元素

>>> root.xpath('//*')  

[<Element bookstore at 0x2c9cc88>, <Element book at 0x2d888c8>, <Element title at 0x2d88738>, <Element price at 0x2d88878>, <Element book at 0x2ca20a8>, <Element title at 0x2d88940>, <Element price at 0x2d88a08>]


# 選取根節點的全部具備屬性節點的title元素

>>> root.xpath('//title[@*]')  

[<Element title at 0x2d88738>, <Element title at 0x2d88940>]


# 選取當前節點下全部節點。'\n ' 是文本節點。

>>> root.xpath('node()')

['\n ', <Element book at 0x2d888c8>, '\n ', <Element book at 0x2d88878>, '\n']


# 選取根節點全部後代節點,包括元素、屬性、文本。

>>> root.xpath('//node()')

[<Element bookstore at 0x2c9cc88>, '\n ', <Element book at 0x2d888c8>, '\n ', <Element title at 0x2d88738>, 'Harry Potter', '\n ', <Element price at 0x2d88940>, '29.99', '\n ', '\n ', <Element book at 0x2d88878>, '\n ', <Element title at 0x2ca20a8>, 'Learning XML', '\n ', <Element price at 0x2d88a08>, '39.95', '\n ', '\n']

或條件選取

使用 "|" 運算符,你能夠選取符合「或」條件的若干路徑。

# 選取全部book的title元素或者price元素

>>> root.xpath('//book/title|//book/price')  

[<Element title at 0x2d88738>, <Element price at 0x2d88940>, <Element title at 0x2ca20a8>, <Element price at 0x2d88a08>]


# 選擇全部title或者price元素

>>> root.xpath('//title|//price')  

[<Element title at 0x2d88738>, <Element price at 0x2d88940>, <Element title at 0x2ca20a8>, <Element price at 0x2d88a08>]


# 選擇book子元素title或者所有的price元素

>>> root.xpath('/bookstore/book/title|//price')

[<Element title at 0x2d88738>, <Element price at 0x2d88940>, <Element title at 0x2ca20a8>, <Element price at 0x2d88a08>]

 

 

lxml 用法

首先咱們利用它來解析 HTML 代碼,先來一個小例子來感覺一下它的基本用法。

from lxml import etree
text = '''
<div>
    <ul>
         <li class="item-0"><a href="link1.html">first item</a></li>
         <li class="item-1"><a href="link2.html">second item</a></li>
         <li class="item-inactive"><a href="link3.html">third item</a></li>
         <li class="item-1"><a href="link4.html">fourth item</a></li>
         <li class="item-0"><a href="link5.html">fifth item</a>
     </ul>
 </div>
'''
html = etree.HTML(text)
result = etree.tostring(html)
print(result)

首先咱們使用 lxml 的 etree 庫,而後利用 etree.HTML 初始化,而後咱們將其打印出來。

其中,這裏體現了 lxml 的一個很是實用的功能就是自動修正 html 代碼,你們應該注意到了,最後一個 li 標籤,其實我把尾標籤刪掉了,是不閉合的。不過,lxml 由於繼承了 libxml2 的特性,具備自動修正 HTML 代碼的功能。

因此輸出結果是這樣的

<html><body>
<div>
    <ul>
         <li class="item-0"><a href="link1.html">first item</a></li>
         <li class="item-1"><a href="link2.html">second item</a></li>
         <li class="item-inactive"><a href="link3.html">third item</a></li>
         <li class="item-1"><a href="link4.html">fourth item</a></li>
         <li class="item-0"><a href="link5.html">fifth item</a></li>
</ul>
 </div>

</body></html>

不只補全了 li 標籤,還添加了 body,html 標籤。

 

文件讀取

除了直接讀取字符串,還支持從文件讀取內容。好比咱們新建一個文件叫作 hello.html,內容爲

<div>
    <ul>
         <li class="item-0"><a href="link1.html">first item</a></li>
         <li class="item-1"><a href="link2.html">second item</a></li>
         <li class="item-inactive"><a href="link3.html"><span class="bold">third item</span></a></li>
         <li class="item-1"><a href="link4.html">fourth item</a></li>
         <li class="item-0"><a href="link5.html">fifth item</a></li>
     </ul>
 </div>

利用 parse 方法來讀取文件。

from lxml import etree
html = etree.parse('hello.html')
result = etree.tostring(html, pretty_print=True)
print(result)

一樣能夠獲得相同的結果。

 

 

XPath 實例測試

 

python3解析庫 lxml http://www.cnblogs.com/zhangxinqi/p/9210211.html

依然以上一段程序爲例

(1)獲取 全部 的 <li> 標籤

from lxml import etree
html = etree.parse('hello.html')
print type(html)
result = html.xpath('//li')
print result
print len(result)
print type(result)
print type(result[0])

運行結果

<type 'lxml.etree._ElementTree'>
[<Element li at 0x1014e0e18>, <Element li at 0x1014e0ef0>, <Element li at 0x1014e0f38>, <Element li at 0x1014e0f80>, <Element li at 0x1014e0fc8>]
5
<type 'list'>
<type 'lxml.etree._Element'>

可見,etree.parse 的類型是 ElementTree,
經過調用 xpath 之後,獲得了一個列表,包含了 5 個 <li> 元素,每一個元素都是 Element 類型。
獲取全部節點。返回一個列表每一個元素都是Element類型,全部節點都包含在其中

from lxml import etree

html = etree.parse('hello.html', etree.HTMLParser())
result = html.xpath('//*')  # //表明獲取子孫節點,*表明獲取全部

print(type(html))
print(type(result))
print(result)

# 如要獲取li節點,可使用//後面加上節點名稱,而後調用xpath()方法
html.xpath('//li')   # 獲取全部子孫節點的li節點

(2)獲取 子節點

經過 / 或者 // 便可查找元素的 子節點 或者 子孫節點,若是想選擇li節點的全部直接a節點,能夠這樣使用

# 經過追加/a選擇全部li節點的全部直接a節點,由於//li用於選中全部li節點,/a用於選中li節點的全部直接子節點a
result=html.xpath('//li/a')  

(3)獲取 父節點

經過 / 或者 // 能夠查找 子節點 或 子孫節點,那麼要查找父節點可使用 .. 來實現也可使用 parent:: 來獲取父節點

from lxml import etree
from lxml.etree import HTMLParser
text='''
<div>
    <ul>
         <li class="item-0"><a href="link1.html">第一個</a></li>
         <li class="item-1"><a href="link2.html">second item</a></li>
     </ul>
 </div>
'''

html=etree.HTML(text,etree.HTMLParser())
result=html.xpath('//a[@href="link2.html"]/../@class')
result1=html.xpath('//a[@href="link2.html"]/parent::*/@class')
print(result)
print(result1)


'''
['item-1']
['item-1']
'''

(4)屬性 匹配

在選取的時候,咱們還能夠用 @符號 進行屬性過濾。好比,這裏若是要選取 class 爲 link1.html 的 li 節點,能夠這樣實現:

from lxml import etree
from lxml.etree import HTMLParser
text='''
<div>
    <ul>
         <li class="item-0"><a href="link1.html">第一個</a></li>
         <li class="item-1"><a href="link2.html">second item</a></li>
     </ul>
 </div>
'''

html=etree.HTML(text, etree.HTMLParser())
result=html.xpath('//li[@class="link1.html"]')
print(result)

# 獲取 <li> 標籤的全部 class
result = html.xpath('//li/@class')
print(result)

(5)文本 獲取

咱們用XPath中的 text() 方法獲取節點中的文本

from lxml import etree

text='''
<div>
    <ul>
         <li class="item-0"><a href="link1.html">第一個</a></li>
         <li class="item-1"><a href="link2.html">second item</a></li>
     </ul>
 </div>
'''

html=etree.HTML(text,etree.HTMLParser())
result=html.xpath('//li[@class="item-1"]/a/text()') #獲取a節點下的內容
result1=html.xpath('//li[@class="item-1"]//text()') #獲取li下全部子孫節點的內容

print(result)
print(result1)

(6)屬性 獲取

使用 @符號便可獲取節點的屬性,以下:獲取全部li節點下全部a節點的href屬性

result=html.xpath('//li/a/@href')  #獲取a的href屬性
result=html.xpath('//li//@href')   #獲取全部li子孫節點的href屬性

(7)屬性 多值 匹配

若是某個屬性的值有多個時,咱們可使用 contains() 函數來獲取

from lxml import etree

text1='''
<div>
    <ul>
         <li class="aaa item-0"><a href="link1.html">第一個</a></li>
         <li class="bbb item-1"><a href="link2.html">second item</a></li>
     </ul>
 </div>
'''

html=etree.HTML(text1,etree.HTMLParser())
result=html.xpath('//li[@class="aaa"]/a/text()')
result1=html.xpath('//li[contains(@class,"aaa")]/a/text()')

print(result)
print(result1)

#經過第一種方法沒有取到值,經過contains()就能精確匹配到節點了
[]
['第一個']

(8)多 屬性 匹配

另外咱們還可能遇到一種狀況,那就是根據多個屬性肯定一個節點,這時就須要同時匹配多個屬性,此時可用運用and運算符來鏈接使用:

from lxml import etree

text1='''
<div>
    <ul>
         <li class="aaa" name="item"><a href="link1.html">第一個</a></li>
         <li class="aaa" name="fore"><a href="link2.html">second item</a></li>
     </ul>
 </div>
'''

html=etree.HTML(text1,etree.HTMLParser())
result=html.xpath('//li[@class="aaa" and @name="fore"]/a/text()')
result1=html.xpath('//li[contains(@class,"aaa") and @name="fore"]/a/text()')


print(result)
print(result1)


#
['second item']
['second item']

(9)按序 選擇

有時候,咱們在選擇的時候某些屬性可能同時匹配多個節點,但咱們只想要其中的某個節點,如第二個節點或者最後一個節點,這時能夠利用中括號引入索引的方法獲取特定次序的節點:

from lxml import etree

text1='''
<div>
    <ul>
         <li class="aaa" name="item"><a href="link1.html">第一個</a></li>
         <li class="aaa" name="item"><a href="link1.html">第二個</a></li>
         <li class="aaa" name="item"><a href="link1.html">第三個</a></li>
         <li class="aaa" name="item"><a href="link1.html">第四個</a></li> 
     </ul>
 </div>
'''

html=etree.HTML(text1,etree.HTMLParser())

result=html.xpath('//li[contains(@class,"aaa")]/a/text()') #獲取全部li節點下a節點的內容
result1=html.xpath('//li[1][contains(@class,"aaa")]/a/text()') #獲取第一個
result2=html.xpath('//li[last()][contains(@class,"aaa")]/a/text()') #獲取最後一個
result3=html.xpath('//li[position()>2 and position()<4][contains(@class,"aaa")]/a/text()') #獲取第一個
result4=html.xpath('//li[last()-2][contains(@class,"aaa")]/a/text()') #獲取倒數第三個


print(result)
print(result1)
print(result2)
print(result3)
print(result4)


#
['第一個', '第二個', '第三個', '第四個']
['第一個']
['第四個']
['第三個']
['第二個']

這裏使用了last()、position()函數,在XPath中,提供了100多個函數,包括存取、數值、字符串、邏輯、節點、序列等處理功能,它們的具體做用可參考:http://www.w3school.com.cn/xpath/xpath_functions.asp

(10)節點軸 選擇

XPath提供了不少節點選擇方法,包括獲取子元素、兄弟元素、父元素、祖先元素等,示例以下:

from lxml import etree

text1='''
<div>
    <ul>
         <li class="aaa" name="item"><a href="link1.html">第一個</a></li>
         <li class="aaa" name="item"><a href="link1.html">第二個</a></li>
         <li class="aaa" name="item"><a href="link1.html">第三個</a></li>
         <li class="aaa" name="item"><a href="link1.html">第四個</a></li> 
     </ul>
 </div>
'''

html=etree.HTML(text1,etree.HTMLParser())
result=html.xpath('//li[1]/ancestor::*')  #獲取全部祖先節點
result1=html.xpath('//li[1]/ancestor::div')  #獲取div祖先節點
result2=html.xpath('//li[1]/attribute::*')  #獲取全部屬性值
result3=html.xpath('//li[1]/child::*')  #獲取全部直接子節點
result4=html.xpath('//li[1]/descendant::a')  #獲取全部子孫節點的a節點
result5=html.xpath('//li[1]/following::*')  #獲取當前子節以後的全部節點
result6=html.xpath('//li[1]/following-sibling::*')  #獲取當前節點的全部同級節點


#
[<Element html at 0x3ca6b960c8>, <Element body at 0x3ca6b96088>, <Element div at 0x3ca6b96188>, <Element ul at 0x3ca6b961c8>]
[<Element div at 0x3ca6b96188>]
['aaa', 'item']
[<Element a at 0x3ca6b96248>]
[<Element a at 0x3ca6b96248>]
[<Element li at 0x3ca6b96308>, <Element a at 0x3ca6b96348>, <Element li at 0x3ca6b96388>, <Element a at 0x3ca6b963c8>, <Element li at 0x3ca6b96408>, <Element a at 0x3ca6b96488>]
[<Element li at 0x3ca6b96308>, <Element li at 0x3ca6b96388>, <Element li at 0x3ca6b96408>]

# 獲取 <li> 標籤下 href 爲 link1.html 的 <a> 標籤
result = html.xpath('//li/a[@href="link1.html"]')
print result

# 獲取 <li> 標籤下的全部 <span> 標籤 (應爲是全部,因此使用 // )
result = html.xpath('//li//span')

# 獲取 <li> 標籤下的全部 class,不包括 <li>
result = html.xpath('//li/a//@class')
print result

# 獲取最後一個 <li> 的 <a> 的 href
result = html.xpath('//li[last()]/a/@href')
print result

# 獲取倒數第二個元素的內容
result = html.xpath('//li[last()-1]/a')
print result[0].text

# 獲取 class 爲 bold 的標籤名
result = html.xpath('//*[@class="bold"]')
print result[0].tag

以上使用的是XPath軸的用法,更多軸的用法可參考:http://www.w3school.com.cn/xpath/xpath_axes.asp

 

 

 

案例應用:抓取TIOBE指數前20名排行開發語言

 

#!/usr/bin/python3
# -*- coding: utf-8 -*-
# @Author      : 
# @File        : test_1.py
# @Software    : PyCharm
# @description : XXX


import requests
from requests.exceptions import RequestException
from lxml import etree
from lxml.etree import ParseError
import json


def one_to_page(html):
    headers = {
        'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 '
                      '(KHTML, like Gecko) Chrome/67.0.3396.62 Safari/537.36'
    }
    try:
        response = requests.get(html, headers=headers)
        body = response.text  # 獲取網頁內容
        try:
            html = etree.HTML(body, etree.HTMLParser())  # 解析HTML文本內容
            result = html.xpath('//table[contains(@class,"table-top20")]/tbody/tr//text()')  # 獲取列表數據
            pos = 0
            for i in range(20):
                if i == 0:
                    yield result[i:5]
                else:
                    yield result[pos:pos + 5]  # 返回排名生成器數據
                pos += 5
        except ParseError as e:
            print(e.position)
    except RequestException as e:
        print('request is error!', e)


def write_file(data):  # 將數據從新組合成字典寫入文件並輸出
    for i in data:
        sul = {
            '2018年6月排行': i[0],
            '2017年6排行': i[1],
            '開發語言': i[2],
            '評級': i[3],
            '變化率': i[4]
        }
        with open('test.txt', 'a', encoding='utf-8') as f:
            f.write(json.dumps(sul, ensure_ascii=False) + '\n')  # 必須格式化數據
            f.close()
        print(sul)


def main():
    url = 'https://www.tiobe.com/tiobe-index/'
    data = one_to_page(url)
    write_file(data)


if __name__ == '__main__':
    main()


'''
{'2018年6月排行': '1', '2017年6排行': '1', '開發語言': 'Java', '評級': '15.932%', '變化率': '+2.66%'}
{'2018年6月排行': '2', '2017年6排行': '2', '開發語言': 'C', '評級': '14.282%', '變化率': '+4.12%'}
{'2018年6月排行': '3', '2017年6排行': '4', '開發語言': 'Python', '評級': '8.376%', '變化率': '+4.60%'}
{'2018年6月排行': '4', '2017年6排行': '3', '開發語言': 'C++', '評級': '7.562%', '變化率': '+2.84%'}
{'2018年6月排行': '5', '2017年6排行': '7', '開發語言': 'Visual Basic .NET', '評級': '7.127%', '變化率': '+4.66%'}
{'2018年6月排行': '6', '2017年6排行': '5', '開發語言': 'C#', '評級': '3.455%', '變化率': '+0.63%'}
{'2018年6月排行': '7', '2017年6排行': '6', '開發語言': 'JavaScript', '評級': '3.063%', '變化率': '+0.59%'}
{'2018年6月排行': '8', '2017年6排行': '9', '開發語言': 'PHP', '評級': '2.442%', '變化率': '+0.85%'}
{'2018年6月排行': '9', '2017年6排行': '-', '開發語言': 'SQL', '評級': '2.184%', '變化率': '+2.18%'}
{'2018年6月排行': '10', '2017年6排行': '12', '開發語言': 'Objective-C', '評級': '1.477%', '變化率': '-0.02%'}
{'2018年6月排行': '11', '2017年6排行': '16', '開發語言': 'Delphi/Object Pascal', '評級': '1.396%', '變化率': '+0.00%'}
{'2018年6月排行': '12', '2017年6排行': '13', '開發語言': 'Assembly language', '評級': '1.371%', '變化率': '-0.10%'}
{'2018年6月排行': '13', '2017年6排行': '10', '開發語言': 'MATLAB', '評級': '1.283%', '變化率': '-0.29%'}
{'2018年6月排行': '14', '2017年6排行': '11', '開發語言': 'Swift', '評級': '1.220%', '變化率': '-0.35%'}
{'2018年6月排行': '15', '2017年6排行': '17', '開發語言': 'Go', '評級': '1.189%', '變化率': '-0.20%'}
{'2018年6月排行': '16', '2017年6排行': '8', '開發語言': 'R', '評級': '1.111%', '變化率': '-0.80%'}
{'2018年6月排行': '17', '2017年6排行': '15', '開發語言': 'Ruby', '評級': '1.109%', '變化率': '-0.32%'}
{'2018年6月排行': '18', '2017年6排行': '14', '開發語言': 'Perl', '評級': '1.013%', '變化率': '-0.42%'}
{'2018年6月排行': '19', '2017年6排行': '20', '開發語言': 'Visual Basic', '評級': '0.979%', '變化率': '-0.37%'}
{'2018年6月排行': '20', '2017年6排行': '19', '開發語言': 'PL/SQL', '評級': '0.844%', '變化率': '-0.52%'}
'''

 

 

案例應用:解析 古文網 並打印 詩經 所對應的 URL

 

#!/usr/bin/python3
# -*- coding: utf-8 -*-
# @Author      : 
# @File        : shijing.py
# @Software    : PyCharm
# @description : XXX


import json
import traceback
import requests
from lxml import etree

"""
step1: 安裝 lxml 庫。
step2: from lxml import etree
step3: selector = etree.HTML(網頁源代碼)
step4: selector.xpath(一段神奇的符號)
"""


def parse():
    url = 'https://www.gushiwen.org/guwen/shijing.aspx'
    r = requests.get(url)
    if r.status_code == 200:
        selector = etree.HTML(r.text)
        s_all_type_content = selector.xpath('//div[@class="sons"]/div[@class="typecont"]')
        print(len(s_all_type_content))

        article_list = list()
        for s_type_content in s_all_type_content:
            book_m1 = s_type_content.xpath('.//strong/text()')[0].encode('utf-8').decode('utf-8')
            s_all_links = s_type_content.xpath('.//span/a')
            article_dict = dict()
            for s_link in s_all_links:
                link_name = s_link.xpath('./text()')[0].encode('utf-8').decode('utf-8')
                try:
                    link_href = s_link.xpath('./@href')[0].encode('utf-8').decode('utf-8')
                except BaseException as e:
                    link_href = None
                article_dict[link_name] = link_href
            temp = dict()
            temp[book_m1] = article_dict
            article_list.append(temp)
        print(json.dumps(article_list, ensure_ascii=False, indent=4))

    else:
        print(r.status_code)


if __name__ == '__main__':
    parse()
    pass

 

 

 

CSS 選擇器——cssSelector 定位方式詳解

 

CSS 選擇器 參考手冊:http://www.w3school.com.cn/cssref/css_selectors.asp
CSS 選擇器 :http://www.runoob.com/cssref/css-selectors.html

Selenium之CSS Selector定位詳解:https://www.bbsmax.com/A/MyJxLGE1Jn/

css selector

 

CSS選擇器用於選擇你想要的元素的樣式的模式。

"CSS"列表示在CSS版本的屬性定義(CSS1,CSS2,或對CSS3)。

選擇器 示例 示例說明 CSS
.class .intro 選擇全部class="intro"的元素 1
#id #firstname 選擇全部id="firstname"的元素 1
* * 選擇全部元素 2
element p 選擇全部<p>元素 1
element,element div,p 選擇全部<div>元素和<p>元素 1
element element div p 選擇<div>元素內的全部<p>元素 1
element>element div>p 選擇全部父級是 <div> 元素的 <p> 元素 2
element+element div+p 選擇全部緊接着<div>元素以後的<p>元素 2
[attribute] [target] 選擇全部帶有target屬性元素 2
[attribute=value] [target=-blank] 選擇全部使用target="-blank"的元素 2
[attribute~=value] [title~=flower] 選擇標題屬性包含單詞"flower"的全部元素 2
[attribute|=language] [lang|=en] 選擇 lang 屬性以 en 爲開頭的全部元素 2
:link a:link 選擇全部未訪問連接 1
:visited a:visited 選擇全部訪問過的連接 1
:active a:active 選擇活動連接 1
:hover a:hover 選擇鼠標在連接上面時 1
:focus input:focus 選擇具備焦點的輸入元素 2
:first-letter p:first-letter 選擇每個<P>元素的第一個字母 1
:first-line p:first-line 選擇每個<P>元素的第一行 1
:first-child p:first-child 指定只有當<p>元素是其父級的第一個子級的樣式。 2
:before p:before 在每一個<p>元素以前插入內容 2
:after p:after 在每一個<p>元素以後插入內容 2
:lang(language) p:lang(it) 選擇一個lang屬性的起始值="it"的全部<p>元素 2
element1~element2 p~ul 選擇p元素以後的每個ul元素 3
[attribute^=value] a[src^="https"] 選擇每個src屬性的值以"https"開頭的元素 3
[attribute$=value] a[src$=".pdf"] 選擇每個src屬性的值以".pdf"結尾的元素 3
[attribute*=value] a[src*="runoob"] 選擇每個src屬性的值包含子字符串"runoob"的元素 3
:first-of-type p:first-of-type 選擇每一個p元素是其父級的第一個p元素 3
:last-of-type p:last-of-type 選擇每一個p元素是其父級的最後一個p元素 3
:only-of-type p:only-of-type 選擇每一個p元素是其父級的惟一p元素 3
:only-child p:only-child 選擇每一個p元素是其父級的惟一子元素 3
:nth-child(n) p:nth-child(2) 選擇每一個p元素是其父級的第二個子元素 3
:nth-last-child(n) p:nth-last-child(2) 選擇每一個p元素的是其父級的第二個子元素,從最後一個子項計數 3
:nth-of-type(n) p:nth-of-type(2) 選擇每一個p元素是其父級的第二個p元素 3
:nth-last-of-type(n) p:nth-last-of-type(2) 選擇每一個p元素的是其父級的第二個p元素,從最後一個子項計數 3
:last-child p:last-child 選擇每一個p元素是其父級的最後一個子級。 3
:root :root 選擇文檔的根元素 3
:empty p:empty 選擇每一個沒有任何子級的p元素(包括文本節點) 3
:target #news:target 選擇當前活動的#news元素(包含該錨名稱的點擊的URL) 3
:enabled input:enabled 選擇每個已啓用的輸入元素 3
:disabled input:disabled 選擇每個禁用的輸入元素 3
:checked input:checked 選擇每一個選中的輸入元素 3
:not(selector) :not(p) 選擇每一個並不是p元素的元素 3
::selection ::selection 匹配元素中被用戶選中或處於高亮狀態的部分 3
:out-of-range :out-of-range 匹配值在指定區間以外的input元素 3
:in-range :in-range 匹配值在指定區間以內的input元素 3
:read-write :read-write 用於匹配可讀及可寫的元素 3
:read-only :read-only 用於匹配設置 "readonly"(只讀) 屬性的元素 3
:optional :optional 用於匹配可選的輸入元素 3
:required :required 用於匹配設置了 "required" 屬性的元素 3
:valid :valid 用於匹配輸入值爲合法的元素 3
:invalid :invalid 用於匹配輸入值爲非法的元素 3

 

CSS選擇器的常見語法:

 

1.  根據 標籤訂位 tagName (定位的是一組,多個元素)
        find_element_by_css_selector("div")

2. 根據 id屬性 定位 (注意 id 使用 # 表示)
        find_element_by_css_selector("#eleid")
        find_element_by_css_selector("div#eleid")
        
3. 根據 className 屬性 定位(注意 class 屬性 使用.)

        兩種方式:前面加上 tag 名稱。也能夠不加。若是不加 tag 名稱時,點不能省略。
        find_element_by_css_selector('.class_value')       
        find_element_by_css_selector("div.eleclass")
        find_element_by_css_selector('tag_name.class_value')

        有的 class_value 比較長,並且中間有空格時,不能把空格原樣寫進去,那樣不能識別。
        這時,空格用點代替,前面要加上 tag_name。
        driver.find_element_by_css_selector('div.panel.panel-email').click()
        # <p class="important warning">This paragraph is a very important warning.</p>
        driver.find_element_by_css_selector('.important')
        driver.find_element_by_css_selector('.important.warning')
        
4. 根據元素屬性定位
        兩種方式,能夠在前面加上tag名稱,也能夠不加。
        find_element_by_css_selector("[attri_name='attri_value']")
        find_element_by_css_selector("input[type='password']").send_keys('密碼')
        find_element_by_css_selector("[type='password']").send_keys('密碼')
    4.1 精確匹配:
        find_element_by_css_selector("div[name=elename]")  #屬性名=屬性值,精確值匹配
        find_element_by_css_selector("a[href]") #是否存在該屬性,判斷a元素是否存在href屬性

    注意:若是 class屬性值 裏帶空格,用.來代替空格
    4.2 模糊匹配
        find_element_by_css_selector("div[name^=elename]") #從起始位置開始匹配
        find_element_by_css_selector("div[name$=name2]") #從結尾匹配
        find_element_by_css_selector("div[name*=name1]") #從中間匹配,包含
    4.3 多屬性匹配
        find_element_by_css_selector("div[type='eletype][value='elevalue']") #同時有多屬性
        find_element_by_css_selector("div.eleclsss[name='namevalue'] #選擇class屬性爲eleclass而且name爲namevalue的div節點
        find_element_by_css_selector("div[name='elename'][type='eletype']:nth-of-type(1) #選擇name爲elename而且type爲eletype的第1個div節點

5. 定位子元素 (A>B)
        find_element_by_css_selector("div#eleid>input") #選擇id爲eleid的div下的全部input節點
        find_element_by_css_selector("div#eleid>input:nth-of-type(4) #選擇id爲eleid的div下的第4個input節點
        find_element_by_css_selector("div#eleid>nth-child(1)") #選擇id爲eleid的div下的第一個子節點

6. 定位後代元素 (A空格B)
        find_element_by_css_selector("div#eleid input") #選擇id爲eleid的div下的全部的子孫後代的 input 節點
        find_element_by_css_selector("div#eleid>input:nth-of-type(4)+label #選擇id爲eleid的div下的第4個input節點的相鄰的label節點
        find_element_by_css_selector("div#eleid>input:nth-of-type(4)~label #選擇id爲eleid的div下的第4個input節點以後中的全部label節點

7. 不是 ( 否 )
        find_element_by_css_selector("div#eleid>*.not(input)") #選擇id爲eleid的div下的子節點中不爲input 的全部子節點
        find_element_by_css_selector("div:not([type='eletype'])") #選擇div節點中type不爲eletype的全部節點

8. 包含Bycontent
        find_element_by_css_selector("li:contains('Goa')") # <li>Goat</li>
        find_element_by_css_selector("li:not(contains('Goa'))) # <li>Cat</li>

9. by index
        find_element_by_css_selector("li:nth(5)")

10. 路徑法
        兩種方式,能夠在前面加上 tag 名稱,也能夠不加。注意它的層級關係使用大於號">"。
        find_element_by_css_selector("form#loginForm>ul>input[type='password']").send_keys('密碼')

高階:

 

 

基本css選擇器

CSS 選擇器中,最經常使用的選擇器 以下:

選擇器 描述 舉例
* 通配選擇器,選擇全部的元素 *
<type> 選擇特定類型的元素,支持基本HTML標籤 h1
.<class> 選擇具備特定class的元素。 .class1
<type>.<class> 特定類型和特定class的交集。(直接將多個選擇器連着一塊兒表示交集) h1.class1
#<id> 選擇具備特定id屬性值的元素 #id1

 

屬性選擇器

除了最基本的核心選擇器外,還有能夠 基於屬性 的 屬性選擇器

選擇器 描述 舉例
[attr] 選取定義attr屬性的元素,即便該屬性沒有值 [placeholder]
[attr="val"] 選取attr屬性等於val的元素 [placeholder="請輸入關鍵詞"]
[attr^="val"] 選取attr屬性開頭爲val的元素 [placeholder^="請輸入"]
[attr$="val"] 選取attr屬性結尾爲val的元素 [placeholder$="關鍵詞"]
[attr*="val"] 選取attr屬性包含val的元素 [placeholder*="入關"]
[attr~="val"] 選取attr屬性包含多個空格分隔的屬性,其中一個等於val的元素 [placeholder~="關鍵詞"]
[attr|="val"] 選取attr屬性等於val的元素或第一個屬性值等於val的元素 [placeholder|="關鍵詞"]

        <p class="important warning">This paragraph is a very important warning.</p>
        selenium舉例: (By.CSS_SELECTOR,'p[class="import warning"]') 
        屬性與屬性的值須要徹底匹配,如上面用p[class='impprtant']就定位不到; 
        部分屬性匹配:(By.CSS_SELECTOR,'p[class~="import warning"]'); 
        子串匹配&特定屬性匹配: 
        [class^="def"]:選擇 class 屬性值以 "def" 開頭的全部元素 
        [class$="def"]:選擇 class 屬性值以 "def" 結尾的全部元素 
        [class*="def"]:選擇class 屬性值中包含子串 "def" 的全部元素 
        [class|="def"]:選擇class 屬性值等於"def"或以"def-"開頭的元素(這個是特定屬性匹配)

 

關係選擇器

有一些選擇器是基於層級之間的關係,這類選擇器稱之爲關係選擇器

選擇器 描述 舉例
<selector> <selector> 第二個選擇器爲第一個選擇器的後代元素,選取第二個選擇器匹配結果 .class1 h1
<selector> > <selector> 第二個選擇器爲第一個選擇器的直接子元素,選取第二個選擇器匹配結果 .class1 > *
<selector> + <selector> 第二個選擇器爲第一個選擇器的兄弟元素,選取第二個選擇器的下一兄弟元素 .class1 + [lang]
<selector> ~ <selector> 第二個選擇器爲第一個選擇器的兄弟元素,選取第二個選擇器的所有兄弟元素 .class1 ~ [lang]

        選擇 某個元素 的 後代的元素: 
        selenium舉例:(By.CSS_SELECTOR,‘div button’)
        div元素的全部的後代元素中標籤爲button元素,無論嵌套有多深

        選擇 某個元素 的 子代元素: 
        selenium舉例:(By.CSS_SELECTOR,‘div > button’)
        div元素的全部的子代元素中標籤爲button元素(>符號先後的空格無關緊要)

        一個元素很差定位時,它的兄長元素很起眼,能夠藉助兄長來揚名,所以不妨稱之爲 "弟弟選擇器".
        即選擇某個元素的弟弟元素(先爲兄,後爲弟): 
        selenium舉例: (By.CSS_SELECTOR,'button + li')
        button與li屬於同一父元素,且button與li相鄰,選擇button下標籤爲li的元素

 

聯合選擇器與反選擇器

利用 聯合選擇器與反選擇器,能夠實現 與和或 的關係。

選擇器 描述 舉例
<selector>,<selector> 屬於第一個選擇器的元素或者是屬於第二個選擇器的元素 h1, h2
:not(<selector>) 不屬於選擇器選中的元素 :not(html)

 

僞元素和僞類選擇器

CSS選擇器支持了 僞元素和僞類選擇器。

:active 鼠標點擊的元素
:checked 處於選中狀態的元素
:default 選取默認值的元素
:disabled 選取處於禁用狀態的元素
:empty 選取沒有任何內容的元素
:enabled 選取處於可用狀態的元素
:first-child 選取元素的第一個子元素
:first-letter 選取文本的第一個字母
:first-line 選取文本的第一行
:focus 選取獲得焦點的元素
:hover 選取鼠標懸停的元素
:in-range 選取範圍以內的元素
:out-of-range 選取範圍以外的元素
:lang(<language>) 選取lang屬性爲language的元素
:last-child 選取元素的最後一個子元素