爬虫必备知识:https://blog.51cto.com/u_11555417/5204144
1、标签 语言
XML是可扩展标记语言(Extensible Markup Language)的缩写
标签语言是一类用于标记和描述文档结构、数据或内容的计算机语言。标签语言通常由标签(tags)组成,用于标识文本或数据的不同部分,并定义这些部分之间的关系或格式。常见的标签语言:
- HTML (HyperText Markup Language):用途:HTML 是用于创建和设计网页的标签语言。它定义了网页的结构,使用标签来描述网页元素(如标题、段落、链接、图像等)
- XML (Extensible Markup Language):XML是可扩展标记语言,是一种存储和传输数据的通用标签语言。它不预定义任何标签,允许用户自定义标签来描述数据的结构。
- XHTML (Extensible HyperText Markup Language):用途:XHTML 是 HTML 的一种严格化版本,结合了 XML 的规则。XHTML 保持了 HTML 的语义,但遵循更严格的语法规则(如标签必须关闭)。
- SGML (Standard Generalized Markup Language):用途:SGML 是一种元语言,HTML 和 XML 都基于 SGML。它用于定义文档结构和标签,适用于各种文档类型。
- Markdown:用途:Markdown 是一种轻量级标记语言,常用于编写文档和格式化文本,广泛应用于博客、GitHub 等平台。它使用简洁的语法来表示格式(如标题、列表、加粗等)。
- LaTeX:是一种用于排版和出版的标记语言,广泛用于学术论文和书籍的编写。它非常适合处理复杂的数学公式和图表。
- MathML (Mathematical Markup Language);用途:MathML 是一种基于 XML 的语言,专门用于表示数学符号和公式,常用于科学和工程领域。
- YAML (YAML Ain't Markup Language):用途:虽然 YAML 的名字包含“标记语言”,但它更常用于配置文件和数据序列化。它通过缩进和冒号语法来表示数据结构。
XPath 是 XML 的路径语言,主要查找 XML(标准通用标记语言的子集)文档中某部分位置。
XPath 基于 XML 的树状结构,提供在数据结构树中找寻节点的能力。 XPath 同样也支持HTML。
XPath 是一门小型的查询语言。python 中 lxml库 使用的是 Xpath 语法,是效率比较高的解析方法。
2、Python 解析 XML / HTMl
lxml 库
lxml 用法 官方文档:http://lxml.de/index.html
lxml 是 Python 中用于处理 XML 和 HTML 的库,并且功能丰富易于使用。
步骤1:安装 lxml 库 :pip3 install lxml
步骤2:from lxml import etree # etree 表示 ElementTree(元素树),etree会自动修复HTML文本节点
步骤3:lxml 提供如下方式输入文本:
selector = etree.fromstring():解析字符串
selector = etree.HTML():解析HTML对象
selector = etree.XML():解析XML对象
selector = etree.parse():解析文件类型对象
步骤4:selector.xpath("xpath表达式")
etree.fromstring()
from lxml import etree
# 示例 XML 字符串
xml_data = '''
<bookstore>
<book category="fiction">
<title lang="en">Harry Potter</title>
<author>J.K. Rowling</author>
<year>2005</year>
<price>29.99</price>
</book>
<book category="cooking">
<title lang="en">Everyday Italian</title>
<author>Giada De Laurentiis</author>
<year>2005</year>
<price>30.00</price>
</book>
</bookstore>
'''
# 解析 XML 字符串
root = etree.fromstring(xml_data)
# 打印根节点的标签
print(root.tag) # 输出: bookstore
# 使用 XPath 查找所有书的标题
titles = root.xpath("//book/title/text()")
print(titles) # 输出: ['Harry Potter', 'Everyday Italian']
etree.HTML()
import re
import requests
import lxml.html
from lxml import etree
from pprint import pprint
def main():
html = '''
<div id="content">
<ul id="useful">
<li>有效信息1</li>
<li>有效信息2</li>
</ul>
<ul id="useless">
<li>无效信息1</li>
<li>无效信息2</li>
</ul>
</div>
<div id="url">
<a href="https://www.bing.com">点击打开bing</a>
<a href="https://www.google.com" title="google搜索">点击打开google</a>
</div>
'''
selector = etree.HTML(text=html)
# 提取 li 中的有效信息
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__':
main()
etree.XML
from lxml import etree
root_node = etree.XML('<root><a><b/></a></root>')
print(etree.tostring(root_node))
print(etree.tostring(root_node, xml_declaration=True))
print(etree.tostring(root_node, xml_declaration=True, encoding='utf-8'))
etree.parse()
from lxml import etree
# 解析 XML 文件
tree = etree.parse('books.xml')
# 获取根元素
root = tree.getroot()
# 打印根节点的标签
print(root.tag)
# 使用 XPath 查找特定元素
for book in root.xpath("//book"):
title = book.find('title').text
author = book.find('author').text
print(f"Title: {title}, Author: {author}")
处理 非标准的 html
lxml 自动修复标签属性两侧缺失的引号,并闭合标签。
from lxml import etree
# 指定解析器HTMLParser会根据文件修复HTML文件中缺失的如声明信息
html = etree.parse('test.html', etree.HTMLParser())
result = etree.tostring(html) # 解析成字节
result_list = etree.tostringlist(html) # 解析成列表
import lxml.html
test_html = "<ul class=country> <li>Area <li id=aa>Population</ul>"
tree = lxml.html.fromstring(test_html)
print(f'type(tree) ---> {type(tree)}')
# pretty_print: 优化输出,输出换行符
# fixed_html = lxml.html.tostring(tree, pretty_print=True)
fixed_html = lxml.html.tostring(tree) # 转换为字节
print(f'type(fixed_html) ---> {type(fixed_html)}')
print(fixed_html)
使用 CSS 选择器
选择所有标签 :*
选择<a>标签:a
选择所有 class="link"的元素: .link
选择 class="link" 的 <a>标签: a.link
选择 id= "home" 的 <a>标签: a#home
选择父元素为 <a> 标签的所有 <span>子标签: a > span
选择<a>标签内部的所有 <span>标签: a span
选择 title 属性为 "Home" 的所有<a>标签: a[title=Home]
import lxml.html
import requests
from fake_useragent import UserAgent
from pprint import pprint
ua = UserAgent()
header = {
"User-Agent": ua.random
}
html = requests.get("https://www.baidu.com/", headers=header)
html = html.content.decode("utf8")
# pprint(html)
tree = lxml.html.fromstring(html)
tag_div_list = tree.cssselect('div#head .mnav')
# 输出文本内容
# area = td.text_content()
for tag_div in tag_div_list:
pprint(tag_div.text)
编辑 HTML / XML
也可以使用 lxml 来生成新的 XML 文档或修改现有的 XML 数据:
from lxml import etree
import lxml.html as HTML
"""
Element是 XML处理的核心类,
Element对象可以直观的理解为 XML的节点,大部分 XML节点的处理都是围绕该类进行的。
这部分包括三个内容:节点的操作、节点属性的操作、节点内文本的操作。
"""
# 1.创建 element
root = etree.Element('root')
print(root, root.tag)
# 2.添加子元素,并为子节点添加属性
body = etree.SubElement(root, 'body')
body.text = 'body_text' # 设置属性和值
body.set('class', 'body_class') # 设置属性和值
etree.SubElement(root, 'child0').text = 'child0'
child1 = etree.SubElement(root, 'child1')
child2 = etree.SubElement(root, 'child2')
book = etree.SubElement(root, "book", category="fiction")
title = etree.SubElement(book, "title", lang="en")
title.text = "The Great Gatsby"
root.append(etree.Element('child3', interesting='sss'))
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))
# 输出整个节点
xml_string = etree.tostring(root, encoding='UTF-8', pretty_print=True).decode()
print(xml_string)
# 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', encoding='utf-8') as f:
a = HTML.document_fromstring(f.read())
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'))
'''以上是文件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)
节点、属性、文本
Element类 是 XML 处理的核心类,Element 对象可以直观的理解为XML的节点,大部分XML节点的处理都是围绕该类进行的。这部分包括三个内容:
- 节点的操作
from lxml import etree root = etree.Element('root') # 创建Element对象 print(root.tag) # 使用tag属性,获取节点的名称 print(etree.tostring(root)) # 使用tostring方法输出XML内容 # 添加子节点。第一个参数为父节点(Element对象),第二个参数为子节点名称。 child1 = etree.SubElement(root, 'child1') child2 = etree.SubElement(root, 'child2') child3 = etree.SubElement(root, 'child3') # 使用remove方法删除指定节点,clear方法清空所有节点。 root.remove(child1) # 删除指定子节点 print(etree.tostring(root)) root.clear() print(etree.tostring(root)) # 以列表的方式操作子节点 child = root[0] # 下标访问 print(child.tag) print(len(root)) # 子节点数量 root.index(child2) # 获取索引号 for child in root: # 遍历 print(child.tag) root.insert(0, etree.Element('child0')) # 插入 start = root[:1] # 切片 end = root[-1:] print(start[0].tag) print(end[0].tag) root.append(etree.Element('child4') ) # 尾部添加 print(etree.tostring(root)) # 获取父节点。使用getparent方法可以获取父节点。 print(child1.getparent().tag)
- 节点的属性。属性 是以 key-value 的方式存储的,就像字典一样
from lxml import etree root = etree.Element('root', interesting='totally') # 创建Element对象 root.set('hello', 'haha') # 设置属性和值 print(etree.tostring(root)) print(root.get('interesting')) # 获取属性值 print(sorted(root.keys())) # keys方法 获取所有的属性名 # items方法获取所有的键值对 for name, value in sorted(root.items()): print(f'{name} ---> {value}') # 也可以用attrib属性一次拿到所有的属性及属性值存于字典 attributes = root.attrib print(attributes) attributes['good'] = 'Bye' # 字典的修改影响节点 print(root.get('good'))
- 节点内的文本。可以使用 text 和 tail 属性、或 XPath 的方式来访问文本内容。
from lxml import etree root = etree.Element('root') root.text = 'Hello, World!' print(root.text) print(etree.tostring(root)) html = etree.Element('html') body = etree.SubElement(html, 'body') body.text = 'Text' print(etree.tostring(html)) br = etree.SubElement(body, 'br') print(etree.tostring(html)) br.tail = 'Tail' print(etree.tostring(br)) print(etree.tostring(html)) print(etree.tostring(html, method='text')) # xpath print(html.xpath('string()')) print(html.xpath('//text()')) print(html.xpath('//text()')[0]) print(html.xpath('//text()')[0].getparent().tag) print(html.xpath('//text()')[0].is_text) print(html.xpath('//text()')[0].is_tail)
scrapy 库:xpath、css,re
使用 xpath 选择器
import scrapy
class JobboleSpider(scrapy.Spider):
name = 'jobbole'
allowed_domains = ['blog.jobbole.com']
start_urls = ['http://blog.jobbole.com/']
def parse(self, response):
re_selector = response.xpath('//*[@id="post-110287"]/div[1]/h1/text()')
使用 CSS 选择器
response.css(".entry-header h1").extract()
response.css(".entry-header h1::text").extract()[0]response.css("p.entry-meta-hide-on-mobile::text").extract()[0].strip().replace(" ·","")
response.css(".vote-post-up h10::text").extract()[0]
使用 re
scrapy 的 response 对象不能直接调用 re 和 re_first 方法。可以先调用 xpath 或者css方法,再使用正则进行匹配
response.css(".bookmark-btn::text").re('.*?(\d+).*')[0]
response.xpath('.').re_first('正则表达式')
使用 Selector
Selector 不一定非要在 Scrapy 中使用,也可以独立使用。使用 Selector 这个类来构建一个选择器对象,然后调用它的相关方法(如xpath css等)来提取数据。示例:构建 Selector 对象来提取数据:
from scrapy import Selector
body = '<html><head><title>hello world</title></head></html>'
selector = Selector(text=body)
title = selector.xpath('//title/text()').extract_first()
print(title)
普通的请求变成 scrapy 的 response
scrapy 的 response 有一个属性 selector,调用 response.selector 返回的内容就相当于用 response 的 text 构造了一个 Selectr 对象。通过这个 Selector 对象就可以调用 xpath、css 等解析方法提取数据。
Scrapy 提供了两个实用的快捷方法:response.xpath 和 response.css,二者的功能完全等同于 response.selector.xpath 和 response.selector.css。为了省事可以统一直接调用 response.xpath 和 response.css
TextResponse:向基类 Response 添加编码能力 ,这意味着仅用于二进制数据,例如图像,声音或任何媒体文件。
- encoding(string) - 是一个字符串,包含用于此响应的编码。如果你创建一个TextResponse具有unicode主体的对象,它将使用这个编码进行编码(记住body属性总是一个字符串)。如果encoding是None(默认值),则将在响应标头和正文中查找编码。 TextResponse除了标准对象之外,对象还支持以下属性Response
- text - 响应体,如unicode。
- body_as_unicode() - 同样text,但可用作方法。保留此方法以实现向后兼容; 请喜欢response.text
其他的 xxxRespose 都最终继承 Respose,可以通过查看源码了解对应 xxxResponse的使用。
import requests
from scrapy.http import HtmlResponse
def func_1():
url = 'https://www.gushiwen.cn/'
resp = requests.get(url)
resp.encoding = 'utf-8'
response = HtmlResponse(url=resp.url, body=resp.text, encoding=resp.encoding)
tag_a_list = response.xpath('//div[@class="right"]//div[@class="sons"]//a')
list(map(lambda x=None: print(x), tag_a_list.extract()))
for tag_a in tag_a_list:
text = tag_a.xpath("./text()").extract_first()
href = tag_a.xpath("./@href").extract_first()
print(f'{text} ---> {href}')
tag_a_list = response.css('div[class="right"] div[class="sons"] a')
list(map(lambda x=None: print(x), tag_a_list.extract()))
# print(response.css('').re())
if __name__ == '__main__':
func_1()
LinkExtractor 提取链接
Link Extractors 文档
- :http://doc.scrapy.org/en/latest/topics/link-extractors.html
- :https://www.osgeo.cn/scrapy/topics/link-extractors.html
链接提取器( Link Extractors ) 的目的就是从 scrapy.http.Response 中提取链接。
导入包:scrapy.linkextractors import LinkExtractor
类定义:class scrapy.linkextractors.lxmlhtml.LxmlLinkExtractor(allow=(), deny=(), allow_domains=(), deny_domains=(), deny_extensions=None, restrict_xpaths=(), restrict_css=(), tags=('a', 'area'), attrs=('href',), canonicalize=False, unique=True, process_value=None, strip=True)
参数 解释:
- allow (str or list): 一个正则表达式(或正则表达式列表),(绝对)urls 必须匹配才能提取。如果没有给出(或为空),它将匹配所有链接。
- deny (str or list): 一个正则表达式(或正则表达式列表),(绝对)urls 必须匹配才能排除(即不提取)。它优先于 allow 参数。如果没有给出(或为空),它不会排除任何链接。,可以 和 allow 配合一起用,前后夹击,参数和 allow 一样。
- allow_domains (str or list):允许 的 域名 或者 域名列表。即 会被提取的链接的 domains。其实这个和 spider 类里的 allowdomains 是一个作用,即 抓取哪个域名下的网站。
- deny_domains (str or list):拒绝 的 域名 或者 域名列表。即 不会被提取链接的 domains。和 allowdomains 相反,即 拒绝哪个域名下的网站。
- deny_extensions( list ):包含在提取链接时应该忽略的扩展的单个值或字符串列表。即不允许的扩展名。如果没有给出(默认 是 None),它将默认为 IGNORED_EXTENSIONS 在 scrapy.linkextractors 包中定义的 列表 。(参考: http://www.xuebuyuan.com/296698.html)
- restrict_xpaths (str or list):一个XPath(或XPath的列表),它定义了从Response哪些区域中来提取链接。即 在网页哪个区域里提取链接,可以用 xpath 表达式和 css 表达式这个功能是划定提取链接的位置,让提取更加精准。如果给出,只有那些XPath选择的文本将被扫描链接。参见下面的例子。即 使用 xpath表达式,和allow共同作用过滤链接。
- restrict_css (str or list):一个CSS选择器(或选择器列表),用于定义响应中应提取链接的区域。同 restrict_xpaths。
- tags( str 或 list ):提取链接时要考虑的 标签或标签列表。默认为('a', 'area')。即 默认提取a标签和area标签中的链接
- attrs( list ):在查找要提取的链接时应该考虑的属性或属性列表(仅适用于参数中指定的那些标签tags )。默认为('href',)。即 默认提取 tags 里的 href 属性,也就是 url 链接。
- canonicalize( boolean ):规范化每个提取的 url ( 使用w3lib.url.canonicalize_url)。默认为False。注意,canonicalize_url用于重复检查;它可以更改服务器端可见的URL,因此对于具有规范化URL和原始URL的请求,响应可能不同。如果使用LinkExtractor跟踪链接,则保持默认的canonicalize=False更为可靠。
- unique( boolean ):是否对提取的链接进行过滤。即 这个地址是否要唯一,默认true,重复收集相同的地址没有意义。
- process_value ( callable ) – 它接收来自扫描标签和属性提取每个值,可以修改该值,并返回一个新的,或返回 None 完全忽略链接的功能。如果没有给出,process_value 默认是 lambda x: x。其实就是:接受一个函数,可以立即对提取到的地址做加工,这个作用比较强大。比如,提取用 js 写的链接:
例如,从这段代码中提取链接: <a href="javascript:goToPage('../other/page.html'); return false">Link text</a>你可以使用下面的这个
process_value
函数:def process_value(value): m = re.search("javascript:goToPage\('(.*?)'", value) if m: return m.group(1)
- strip :把 提取 的 地址 前后多余的空格删除,很有必要。whether to strip whitespaces from extracted attributes. According to HTML5 standard, leading and trailing whitespaces must be stripped from
href
attributes of<a>
,<area>
and many other elements,src
attribute of<img>
,<iframe>
elements, etc., so LinkExtractor strips space chars by default. Setstrip=False
to turn it off (e.g. if you’re extracting urls from elements or attributes which allow leading/trailing whitespaces).
示例:
import requests
from scrapy.http import HtmlResponse
from scrapy.linkextractors import LinkExtractor
def main():
__resp = requests.get('https://so.gushiwen.cn/shiwens/')
if 200 == __resp.status_code:
resp = HtmlResponse(url=__resp.url, body=__resp.text, encoding=__resp.encoding)
"""
LinkExtractor 初始化函数 __init__ 的参数
def __init__(
self,
allow=(),
deny=(),
allow_domains=(),
deny_domains=(),
restrict_xpaths=(),
tags=("a", "area"),
attrs=("href",),
canonicalize=False,
unique=True,
process_value=None,
deny_extensions=None,
restrict_css=(),
strip=True,
restrict_text=None,
):
"""
le = LinkExtractor(
restrict_xpaths=('//div[@id="right1"]', '//div[@id="right3"]')
)
links = le.extract_links(resp)
list(map(lambda x=None: print(x), links))
list(map(lambda x=None: print(f'{x.text.strip()} ---> {x.url}'), links))
pass
if __name__ == '__main__':
main()
pass
scrapy 的常用 ImagesPipeline 重写实现:https://www.jianshu.com/p/cd05763d49e8
使用 Scrapy 自带的 ImagesPipeline下载图片,并对其进行分类:https://www.cnblogs.com/Kctrina/p/9523553.html
使用 FilesPipeline 和 ImagesPipeline:https://www.jianshu.com/p/a412c0277f8a
Rule 和 Link Extractors 多用于全站的爬取
爬取规则(Crawling rules):http://doc.scrapy.org/en/latest/topics/spiders.html#crawling-rules
Rule 是在定义抽取链接的规则:class scrapy.contrib.spiders.Rule(link_extractor,callback=None,cb_kwargs=None,follow=None,process_links=None,process_request=None)
参数解释:
- link_extractor:是一个 Link Extractor 对象。其定义了如何从爬取到的 页面(即 response) 提取链接的方式。
- callback:是一个 callable 或 string(该Spider中同名的函数将会被调用)。从 link_extractor 中每获取到链接时将会调用该函数。该回调函数接收一个 response 作为其第一个参数,并返回一个包含 Item 以及 Request 对象(或者这两者的子类)的列表。
- cb_kwargs:包含传递给回调函数的参数(keyword argument)的字典。
- follow:是一个 boolean 值,指定了根据该规则从 response 提取的链接 是否 需要跟进。如果 callback 为 None,follow 默认设置 True,否则默认 False。当 follow 为 True 时:爬虫会从获取的 response 中 取出符合规则的 url,再次进行爬取,如果这次爬取的 response 中还存在符合规则的 url,则再次爬取,无限循环,直到不存在符合规则的 url。 当 follow 为 False 时:爬虫只从 start_urls 的 response 中取出符合规则的 url,并请求。
- process_links:是一个callable或string(该Spider中同名的函数将会被调用)。从link_extrator中获取到链接列表时将会调用该函数。该方法主要是用来过滤。
- process_request:是一个callable或string(该spider中同名的函数都将会被调用)。该规则提取到的每个request时都会调用该函数。该函数必须返回一个request或者None。用来过滤request。
官网示例:
import scrapy
from scrapy.spiders import CrawlSpider, Rule
from scrapy.linkextractors import LinkExtractor
class MySpider(CrawlSpider):
name = 'example.com'
allowed_domains = ['example.com']
start_urls = ['http://www.example.com']
rules = (
# Extract links matching 'category.php' (but not matching 'subsection.php')
# and follow links from them (since no callback means follow=True by default).
Rule(LinkExtractor(allow=('category\.php', ), deny=('subsection\.php', ))),
# Extract links matching 'item.php' and parse them with the spider's method parse_item
Rule(LinkExtractor(allow=('item\.php', )), callback='parse_item'),
)
def parse_item(self, response):
self.logger.info('Hi, this is an item page! %s', response.url)
item = scrapy.Item()
item['id'] = response.xpath('//td[@id="item_id"]/text()').re(r'ID: (\d+)')
item['name'] = response.xpath('//td[@id="item_name"]/text()').get()
item['description'] = response.xpath('//td[@id="item_description"]/text()').get()
item['link_text'] = response.meta['link_text']
url = response.xpath('//td[@id="additional_data"]/@href').get()
return response.follow(url, self.parse_additional_page, cb_kwargs=dict(item=item))
def parse_additional_page(self, response, item):
item['additional_data'] = response.xpath('//p[@id="additional_data"]/text()').get()
return item
示例:
# -*- coding: utf-8 -*-
import os
import requests
from pathlib import Path
from scrapy.spiders import CrawlSpider, Rule
from scrapy.linkextractors import LinkExtractor
class MMSpider(CrawlSpider):
name = 'mm_spider'
allowed_domains = ['www.521609.com']
start_urls = ['http://www.521609.com']
# start_urls = ['http://www.521609.com/xiaoyuanmeinv/10464_5.html']
# 自定义配置。自定义配置会覆盖 setting.py 中配置,即 优先级 大于 setting.py 中配置
custom_settings = {
'USER_AGENT': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) '
'AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.67 Safari/537.36',
# 'LOG_FILE': 'MMSpider.log',
# 'LOG_LEVEL': 'DEBUG'
'LOG_ENABLED': False, # 关闭 scrapy 中的 debug 打印
'LOG_LEVEL': 'INFO'
}
rules = (
# 匹配 包含 xiaohua/ 和 meinv/ 所有 URL
Rule(LinkExtractor(allow=(r'xiaohua/\d+_?\d+', r'meinv/\d+_?\d+')), callback='parse_img', follow=True),
Rule(LinkExtractor(allow=(r'xiaohua/', 'meinv/')), follow=True)
)
# def parse(self, response):
# print(response.url)
# super(MMSpider, self).parse(response)
# pass
def parse_img(self, response):
spider_name = self.name
current_url = response.url
print(f'current_url:{current_url}')
mm_name = response.xpath('//div[@class="title"]/h2/text()').extract_first()
link_extractor = LinkExtractor(
allow='uploads/allimg', # 匹配的 URL
deny=r'(lp\.jpg)$', # 排除的 URL。这里排除掉 以 lp.jpg 结尾 的 缩略图 URL
tags=('img',), # 要提取链接的 标签
attrs=('src',), # 提取的连接
restrict_xpaths='//div[@class="picbox"]', # 在 xpath 指定区域 匹配
deny_extensions='' # 禁用扩展。默认会把 .jpg 扩展名的URL后缀过滤掉,这里禁用。
)
all_links = link_extractor.extract_links(response)
# 可以使用自定义的 item,也可以直接使用 Python 的 dict,
# 因为 item 本身就是一个 python类型的 dict
# img_item = MMItem()
img_item = dict()
image_urls_list = list()
for link in all_links:
print(f'\t{mm_name}:{link.url}')
image_urls_list.append(link.url)
else:
img_item['img_name'] = mm_name
img_item['image_urls'] = image_urls_list
# yield img_item
print(f'\t{img_item}')
for img_url in image_urls_list:
file_name = img_url.split('/')[-1].split('.')[0]
dir_path = f'./{mm_name}'
if not Path(dir_path).is_dir():
os.mkdir(dir_path)
file_path = f'./{mm_name}/{file_name}.jpg'
r = requests.get(url=img_url)
if 200 == r.status_code:
with open(file_path, 'wb') as f:
f.write(r.content)
else:
print(f'status_code:{r.status_code}')
pass
if __name__ == '__main__':
from scrapy import cmdline
cmdline.execute('scrapy crawl mm_spider'.split())
pass
- CrawlSpider 除了从 Spider类 继承过来的属性外,还提供了一个新的属性 rules 来提供跟进链接(link)的方便机制,这个机制更适合从爬取的网页中获取 link 并继续爬取的工作。
- rules 包含一个或多个 Rule 对象的集合。每个 Rule 对爬取网站的动作定义了特定规则。如果多个 Rule 匹配了相同的链接,则根据他们在本属性中被定义的顺序,第一个会被使用。所以:规则的顺序可以决定获取的数据,应该把 精确匹配的规则放在前面,越模糊的规则放在后面。
- CrawlSpider 也提供了一个可复写的方法:parse_start_url(response) 当 start_url 的请求返回时,该方法被调用。该方法分析最初的返回值并必须返回一个 Item 对象或一个 Request 对象或者一个可迭代的包含二者的对象
注意:当编写 CrawlSpider 爬虫 的 规则 时,不要使用 parse 作为回调函数。 由于 CrawlSpider 使用 parse 方法来实现其逻辑,如果覆盖了 parse 方法,CrawlSpider 将会运行失败。
parsel 库
parsel 库可以解析HTML和XML,并支持使用Xpath和CSS选择器对内容进行提取和修改,同时还融合了正则表达式的提取功能。parsel灵活且强大,python 最流行的爬虫框架 Scrapy 的选择器就是基于 parsel 做了二次封装。安装:pip install parsel
from parsel import Selector
html = '''
<div>
<ul>
<li class="item-0">第1个item</li>
<li class="item-1"><a href="link2.html">第2个item</a></li>
<li class="item-0 active"><a href="link3.html"><span class="bold">第3个item</span></a></li>
<li class="item-1 active"><a href="link4.html">第4个item</a></li>
<li class="item-0"><a href="link5.html">第5个item</a></li>
</ul>
</div>
'''
selector = Selector(text=html)
items = selector.css('.item-0')
print(f'{len(items)}, {type(items)}, {items}')
# 用css选择器提取的节点,输出就是xpath属性而不是css属性,因为css是首先被转化成xpath,真正用于节点提取的是xpath。
# 其中css选择器转化为xpath的过程是由底层 csselect 这个库实现的。
items2 = selector.xpath('//li[contains(@class, "item-0")]')
print(f'{len(items2)}, {type(items)}, {items2}')
for item in items:
text = item.xpath('.//text()').get()
print(text)
# get方法只会提取第一个selector对象的结果
result = selector.xpath('//li[contains(@class,"item-0")]//text()').get()
print(result)
# getall方法提取所有selector对象的结果
result = selector.xpath('//li[contains(@class,"item-0")]//text()').getall()
print(result)
# 使用 css。
# *来提取所有子节点,提取文本需要加上::text
result = selector.css(".item-0 *::text").getall()
print(result)
result = selector.css(".item-0.active a::attr(href)").get() # css方法
print(result)
result = selector.xpath("//li[contains(@class,'item-0') and contains(@class,'active')]/a/@href").get()
print(result)
# 如果在调用css方法时,已经提取进一步的结果了,例如提取了节点的文本值,那么re方法就只会针对节点文本值进行提取
result = selector.css(".item-0").re("link.*") # css方法
print(result)
# 用re_first方法来提取第一个符合规则的结果:
result = selector.css(".item-0").re_first('<span class="bold">(.*?)</span>')
print(result)
pyquery 库
安装:pip install pyquery
PyQuery 可以对 XML 文档进行 jQuery 查询。 该 API 尽可能类似于 jquery。PyQuery 使用 lxml 库 实现快速 XML 和 HTML 操作。
3、XPath 语法
XPath 可以在 XML 文档中对元素和属性进行查找遍历。XPath 是 W3C XSLT 标准的主要元素,并且 XQuery 和 XPointer 都构建于 XPath 表达之上。在 XPath 中,有七种类型的节点:元素、属性、文本、命名空间、处理指令、注释以及文档(根)节点。XML 文档是被作为节点树来对待的。树的根被称为文档节点或者根节点。根节点在 xpath 中可以用 "//" 来表示
示例:读取文本并解析节点 (etree 会修复 HTML 文本节点)
from lxml import etree
html_text = '''
<div>
<ul>
<li class="item-0"><a href="link1.html">第0个item</a></li>
<li class="item-1"><a href="link2.html">第1个item</a></li>
<li class="item-0"><a href="link5.html">第2个item</a>
</ul>
</div>
'''
html = etree.HTML(html_text) # 初始化生成一个XPath解析对象
result = etree.tostring(html, encoding='utf-8') # 解析对象输出代码
print(type(html))
print(type(result))
print(result.decode('utf-8'))
with open('test.html', 'w', encoding='utf-8') as f:
f.write(html_text)
# 指定解析器HTMLParser会根据文件修复HTML文件中缺失的如声明信息
html = etree.parse('test.html', etree.HTMLParser())
result = etree.tostring(html) # 解析成字节
print(f'tostring ---> {result}')
result=etree.tostringlist(html) #解析成列表
print(f'tostringlist ---> {result}')
print(type(html))
print(type(result))
节点关系 ( 父、子、同胞、先辈、后代 )
示例 xml 如下
<bookstore>
<book>
<title>Harry Potter</title>
<author>J K. Rowling</author>
<year>2005</year>
<price>29.99</price>
</book>
</bookstore>
- 父 (parent):标签 title、author、year、price 的父标签都是 book
- 子 (children):bookstore的子标签是book,book 的子标签是 title、author、year、price。
- 同胞 (sibling):拥有相同的父的节点。title、author、year 以及 price 元素都是同胞
- 先辈 (ancestor):节点的父、父的父,等等。title 的先辈是 book 和 bookstore
- 后代 (descendant):子子孙孙。bookstore 的后代是 book、title、author、year、price
选取节点
XPath 使用路径表达式在 XML 文档中选取节点。
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 表达式中的运算符:( 此表参考来源:XPath 运算符)
运算符 | 描述 | 实例 | 返回值 |
---|---|---|---|
| | 计算两个节点集 | //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 使用
python 使用 xpath(超详细):https://www.cnblogs.com/mxjhaima/p/13775844.html
W3school Xpath 教程:https://www.w3school.com.cn/xpath/index.asp
简书:Xpath 高级用法:https://www.jianshu.com/p/1575db75670f
函数、高级用法
@属性
//@class 含有 class 属性的标签
//div[@*] 含有属性 的 div
//div[@lang] 含有 lang 属性的 div
//div[@lang='eng'] lang 属性等于 eng 的 div
//div[not(@id)] 不包含id属性 的 div//div[@class='class1 class2']//span
//div[contains(@class, "class1")]//span
/bookstore/book[price>35.00]
/bookstore/book[price>35.00]/title
/bookstore/*
//a[text()='标签的直接文本']
与或非:and、or、not()
//span[@type='radio' and @value='99'] 多条件 (与)
//span[@name='bruce' and text()='bruce1'][1] 多条件 (与)
//span[@name='bruce'][text()='bruce1'][1] 多条件 (与)
//span[@id='bruce1' or text()='bruce2'] 找出多个 (或)not() 函数通常与返回值为 true 或者 false 的函数组合,比如 contains()、starts-with() 等。
//div[@id='identity' and not(contains(@class, 'aaa'))]
//div[@class='seats']//span[contains(@class,'seat') and not(contains(@class,'empty'))]
//div[@class='seats']//span[not(contains(//span[contains(@class, 'seat')]/@class, 'empty'))]
包含:contains()
//div[contains(@class,'关键字')]
//div[contains(text(),'关键字')]//div[contains(@class, "class中包含的关键字")]/a/text()
//div[starts-with(@id,'res')]//table[1]//td[2]//a//span[contains(.,'test') and contains(.,'kpi')]
//li[contains(@class,"aaa")]/a/text()
//li[1][contains(@class,"aaa")]/a/text()
//li[contains(@class,"aaa") and @name="fore"]/a/text()
//li[last()][contains(@class,"aaa")]/a/text()
starts-with()
//div[starts-with(@id,'kw')] 查找以 kw 开头的 div
//div[starts-with(@id,'id123')]
//div[starts-with(@id,'id456')]//table//td[2]//table//td//a//span[contains(.,'test')]
标签 (不)包含 标签
//*[img] 选择那些包含 <img> 标签作为子节点的父标签。
//span[not(a)] 选择那些不包含 <a> 标签的 <span> 标签。
通过 子节点 定位
//div[div[@id='navigation']]
//div[div[@name='listType']]
//div[p[@name='testname']]//div[div[@name='listType']]//img
//td[a//font[contains(text(),'包含的关键字')]]//input[@type='checkbox']
排除 指定 标签
示例:查询 <div> 下的所有子孙节点,并且排除掉 <style> 标签。
//div[contains(@id, "example")]//node()[not(self::style)]
//div[contains(@id, "example")]: 查找整个文档中id 属性包含 "example" 的 <div> 元素。
//node(): 选择这些 <div> 元素的所有子孙节点(可以是文本节点、元素节点、注释等)/node() 如果是这个,则表示 直接子节点。
[not(self::style)]: 筛选子节点,排除 <style> 元素,即只保留非 <style> 的节点。示例:选择所有 <div> 标签且其中不包含任何 <style> 标签的后代。
//div[not(descendant::style)]
索引
注意:xpath 的索引是从 1 开始的。
//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]
//li[@class="movie-name"][1]/text()
//li[1]/a/text() 选取第一个li节点
//li[last()]/a/text() 选取最后一个li节点
//li[position()<3]/a/text() 选取位置小于3的li节点,也就是1和2的
//li[last()-2]/a/text() 选取倒数第三个节点
//li[position()>2 and position()<4][contains(@class,"aaa")]/a/text()
//li[last()-2][contains(@class,"aaa")]/a/text()
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之后的字符匹配
通配符 *
//* 选择文档中任意层级的所有标签。"//" 可以理解为从根节点开始递归查找,* 指任意标签
//span[@*='bruce'] 选择 属性值中有等于 bruce 的所有 span
//*[@name='bruce'] 选择 name属性值是 bruce 的 所有 标签
node()、element()、attribute()、text()、string()
//node() 选择所有类型的节点,包括 元素<element>、文本Text 和 属性<attribute> 节点。
如果只想选择特定类型的节点,可以使用其他的 XPath 轴,如 child(子节点)、descendant(后代节点)、attribute(属性节点)等。
//element() 选择所有的元素节点
//attribute() 选择所有的属性节点
//text() 选择所有的文本节点root.xpath('node()')
root.xpath('//node()')
root.xpath('//book/title|//book/price')
root.xpath('//title|//price')
root.xpath('/bookstore/book/title|//price')注意:节点的文本值不属于属性
text()、/text()、//text()
//a[text()='baidu']
//span[@id='span_id' and contains(text(),'google')]
//div[@class="cls1"]/text() 获取class为cls1的div属于这个节点的文本
//div[@class="cls2"]//text() 获取class为cls2的div节点的子子孙孙的文本string() 函数用来获取多个标签下的所有文本
示例:string(.)
示例:string(//div[@id="id123"])
对于如下的代码提取所有文本:string(//div[@id="id123"])
<div id="id123">
我左青龙,
<span id="tiger">右白虎,
<ul>上朱雀,
<li>下玄武。</li>
</ul>老牛在当中,
</span>
龙头在胸口。
<div>
轴:父 (parent)、子 (children)、兄弟 (sibling)、祖先 (ancestor)、子子孙孙 (descendant)
//li[1]/ancestor::* 返回第一个li节点的所有祖先节点
//li[1]/ancestor::div 返回第一个li节点的祖先div节点.//h3/preceding-sibling::div//text() 当前节点的前一个兄弟div节点的所有文本
//li[1]/attribute::* 返回第一个li节点的所有属性值
//li[1]/child::* # 获取所有直接子节点
//li[1]/descendant::a # 获取所有子孙节点的a节点
//li[1]/following::* # 获取当前子节之后的所有节点
//li[1]/following-sibling::* # 获取当前节点的所有同级节点
//li[1]/child::a[@href="www"] 查找第1个li节点的所有子节点中 href 属性为www的a节点
//li[1]/descendant::span 返回第1个li节点的所有子孙节点中的span节点
下面两个等价:查找父节点中还有class属性的 节点
//a[@href="link4.html"]/../@class
//a[@href="link4.html"]/parent::*/@class
下面两个等价:
//div[span[text()='cur_note']]/descendant::div/span[text()='123']
//div[span[text()='cur_note']]//div/span[text()='123']
//div[span[text()='cur_node']]/parent::div 找父节点是 div 的标签
//div[span[text()='cur_node']]/ancestor::div 找祖先节点是 div 的标签示例:
//input[@id='123']/following-sibling::input 查找下一个兄弟节点
//input[@id='123']/preceding-sibling::span 查找上一个兄弟节点
//span[not(contains(text(),'关键字'))] 查找不包含 "关键字" 字段的span
示例:
//div[starts-with(@id,'res')]//table[1]//td[2]//a//span[contains(.,'test')]/ancestor::div[starts-with(@id,'res')]//table[2]//descendant::a[2]
//img/ancestor::*[2]
/ancestor::*[2]:表示 <img> 标签的第二层祖先(即爷爷标签)。而 [2] 限定了选择第 2 层祖先(爷爷)。也就是说,如果一个 <img> 标签的父标签是 <div>,其祖父标签也是 <div>,则这个表达式会返回祖父标签。
ancestor::* 会找到所有的祖先标签,
//img/ancestor::div[2] 查找文档中所有的 <img> 元素。从每个 <img> 元素开始,向上查找其第二个祖先 <div> 元素。
示例:
//img/preceding-sibling::*//text():获取当前节点之前的所有兄弟其子元素的文本。
//img/preceding-sibling::text(): 获取当前节点之前的兄弟节点中的直接文本。
示例://div[starts-with(@id,'res')]//table[1]//td[2]//span[contains(.,'QuickStart')]/../../descendant::img
from lxml import etree
html_content = '''
<div>
<div>
<p>p1</p>
<span>span1</span>
<p id="target1">target1</p>
<span>span2</span>
<p>p2</p>
<div>
<p>p3</p>
<span>span3</span>
<p id="target2">target2</p>
<span>span4</span>
<p>p4</p>
<div>
<span>span5</span>
<p>p5</p>
</div>
</div>
</div>
</div>
'''
# 解析 HTML
tree = etree.HTML(html_content)
# 定位目标节点
target_node = tree.xpath('//p[@id="target2"]')[0] # 选择具有 id="target2" 的 <p> 节点
# 获取最近的祖先 <div>
ancestor_div = target_node.xpath('ancestor::div[1]')
# 使用 XPath 获取目标节点到祖先之间的所有文本
texts_between = ancestor_div.xpath('.//text()') # 获取所有文本
print(target_node.xpath('ancestor::div[1]/child::*//text()'))
print(target_node.xpath('ancestor::div[1]/child::*/text()'))
# 过滤出目标节点之前的文本
filtered_texts = []
found_target = False
for text in texts_between:
if text.strip() == target_node.text.strip():
found_target = True # 找到目标节点后,开始收集文本
if found_target:
filtered_texts.append(text.strip())
# 打印结果
print("Text from target node to its ancestor:")
for text in filtered_texts:
print(text)
向后(following) / 向前(preceding)
向 后(前) 查找所有节点
following-sibling 可获取当前节点之后的所有同级(兄弟)节点
preceding-sibling 可获取当前节点之前的所有同级(兄弟)节点
//span[@class="fk_cur"]/../following::a 往下的所有a
//span[@class="fk_cur"]/../preceding::a 往上的所有a
//li[1]/following::*[2] 查找第1个<li>节点后面所有节点的第2个节点
//li[1]/following-sibling::*
//li[1]/preceding-sibling::*
示例:豆瓣 爬虫
import re
import json
import datetime
import requests
from lxml import etree
requests.packages.urllib3.disable_warnings()
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
示例:解析 古文网
打印 诗经 所对应的 URL
import json
import traceback
import requests
from lxml import etree
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
4、CSS 选择器
CSS 语法
CSS 选择器 参考手册:http://www.w3school.com.cn/cssref/css_selectors.asp
CSS 选择器 :http://www.runoob.com/cssref/css-selectors.html
选择器 | 示例 | 示例说明 | 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 选择器
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 | 选取元素的最后一个子元素 |
CSS选择器的常见语法
高阶:
Selenium 中使用 CSS Selector
:https://www.bbsmax.com/A/MyJxLGE1Jn/
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. 包含
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('密码')