• python爬虫之JS逆向——网页数据解析


    目录

    一、正则

    1 正则基础

    元字符

    基本使用

    通配符: '.'

    字符集: '[]'

    重复

    位置

    管道符和括号

    转义符

    转义功能

    转义元字符

    2 正则进阶

    元字符组合(常用)

    模式修正符

     re模块的方法

    有名分组

    compile编译

    二、bs4

    1 四种对象

    2 导航文档树

    嵌套选择

    子节点、子孙节点

    父节点、祖先节点

    兄弟节点

    3 搜索文档树

    name参数

      标签名

      正则表达式

      列表

      函数(精致过滤)

    关键字参数

    文本参数

    4 CSS选择器

    三、xpath

    1 路径表达式

    2 谓语

    3 通配符

    4 其他用法

    5 文件操作

    文件句柄

    读操作

    写操作 

    覆盖写

    追加写


    一、正则

    是描述一段文本排列规则的表达式
    正则表达式并不是python的一部分,而是一套独立于编程语言,用于处理复杂文本信息的强大的高级文本操作工具。
    python提供re模块或regex模块来调用正则处理引擎

    正则对字符串的操作:分割、匹配、查找和替换

    1 正则基础

    元字符

    元字符是具有特殊含义的字符

    元字符描述
    []匹配一个中括号中出现的任意一个原子
    [^原子]匹配一个没有在中括号中出现的任意原子
    \转义字符,可以把原子转换特殊元字符,也可以把特殊元字符转换成原子
    ^叫开始边界符或开始锚点符,匹配一行的开头位置
    $叫结束边界符或开始锚点符,匹配一行的结束位置
    .叫通配符、万能通配符或通配元字符,匹配一个除了换行符\n以外任何原子
    *叫星号贪婪符,指定左边原子出现0次或多次
    ?叫非贪婪符,指定左边原子出现0次或1次
    +叫加号贪婪符,指定左边原子出现1次或多次
    {n,m}

    叫数量范围贪婪符,指定左边原子的数量范围,有{n},{n,},{,m},{n,m}四种写法,

    其中n与m必须是非负整数

    |指定原子或正则模式进行二选一或多选一
    ()对原子或正则模式进行捕获提取和分组划分整体操作

    举例和一些特殊组合如下所示

    基本使用
    1. import re
    2. ret1 = re.findall("a", "a,b,c,d,e") # ['a']
    '
    运行
    通配符: '.'
    1. ret2 = re.findall(".", "a,b,c,d,e") # ['a', ',', 'b', ',', 'c', ',', 'd', ',', 'e']
    2. ret2 = re.findall("a.b", "a,b,c,d,e,acb,abb,a\nb,a\tb") # ['a,b', 'acb', 'abb', 'a\tb']
    字符集: '[]'
    1. ret3 = re.findall("[ace]", "a,b,c,d,e") # ['a', 'c', 'e']
    2. ret3 = re.findall("a[bce]f", "af,abf,abbf,acef,aef") # ['abf', 'aef']
    3. ret3 = re.findall("[a-zA-Z]", "a,b,c,d,e,A,V") # ['a', 'b', 'c', 'd', 'e', 'A', 'V']
    4. ret3 = re.findall("[a-zA-Z0-9]", "a,b,c,d,1,e,A,V") # ['a', 'b', 'c', 'd', '1', 'e', 'A', 'V']
    5. ret3 = re.findall("[^0-9]", "a,2,b,c,d,1,e,A,V") # ['a', ',', ',', 'b', ',', 'c', ',', 'd', ',', ',', 'e', ',', 'A', ',', 'V']
    6. # [0-9] == \d [a-zA-Z0-9] == \w
    重复

    '*' :1-多次

    '+' :0-多次

    '?' :0/1次, 也可取消贪婪匹配

    '{m,n}' :m-n次

    贪婪匹配,每次为最多次匹配

    ?取消贪婪匹配

    1. ret4 = re.findall("\d+", "a,b,234,d,6,888") # ['234', '6', '888']
    2. # ?取消贪婪匹配
    3. ret4 = re.findall("\d+?", "a,b,234,d,6,888") # ['2', '3', '4', '6', '8', '8', '8']

    +

    1. ret4 = re.findall("\w", "apple,banana,orange,melon") # ['a', 'p', 'p', 'l', 'e', 'b', 'a', 'n', 'a', 'n', 'a', 'o', 'r', 'a', 'n', 'g', 'e', 'm', 'e', 'l', 'o', 'n']
    2. ret4 = re.findall("\w+", "apple,banana,orange,melon") # ['apple', 'banana', 'orange', 'melon']
    3. ret4 = re.findall("\w+?", "apple,banana,orange,melon") # ['a', 'p', 'p', 'l', 'e', 'b', 'a', 'n', 'a', 'n', 'a', 'o', 'r', 'a', 'n', 'g', 'e', 'm', 'e', 'l', 'o', 'n']

     注:"\w*" == ""和"\w"

    *

    1. ret4 = re.findall("\w*", "apple,banana,orange,melon") # ['apple', '', 'banana', '', 'orange', '', 'melon', '']
    2. ret4 = re.findall("abc*", "abc,abcc,abe,ab") # ['abc', 'abcc', 'ab', 'ab']

    ?

    ret4 = re.findall("\w{6}", "apple,banana,orange,melon")  # ['banana', 'orange']

    {m,n}

    1. ret4 = re.findall("abc?", "abc,abcc,abe,ab") # ['abc', 'abc', 'ab', 'ab']
    2. ret4 = re.findall("abc??", "abc,abcc,abe,ab") # ['ab', 'ab', 'ab', 'ab']
    位置

    '^' :匹配开头符合条件的字符

    '$' :匹配结尾符合条件的字符

    ^

    1. ret5 = re.findall("^\d+", "34,banana,255,orange,5434") # ['34']
    2. ret5 = re.findall("^\d+", "peath,34,banana,255,orange,5434") # []

    $

    1. ret5 = re.findall("\d+$", "34,banana,255,orange,5434") # ['5434']
    2. ret5 = re.findall("\d+$", "peath,34,banana,255,orange") # []
    管道符和括号

    |:或

    ():优先提取/括号

    (?:) 取消模式捕获

    1. ret6 = re.findall(",(\w{5}),", ",apple,banana,peach,orange,melon,") # ['apple', 'peach', 'melon']
    2. ret6 = re.findall("\w+@(163|qq)\.com", "123abc@163.com...789xyz@qq.com") # ['163', 'qq']
    3. ret6 = re.findall("\w+@(?:163|qq)\.com", "123abc@163.com...789xyz@qq.com") # ['123abc@163.com', '789xyz@qq.com']

    转义符

    转义的两个功能

    将一些普通符号赋予特殊功能  \d \w ...

    将特殊符号取消其特殊功能  * . + ...

    转义功能
    ret7 = re.findall("\(abc\)", "(abc)...")  # ['(abc)']
    转义元字符
    特殊模式描述
    \d匹配任意一个数字(0-9)[0-9]
    \D匹配任意一个非数字字符[^0-9]/[^\d]
    \w匹配任意一个字母、数字或下划线(单词字符)[A-Za-z0-9_]
    \W匹配任意一个非字母、非数字、非下划线字符[^A-Za-z0-9_]\[^\w]
    \s匹配任意一个空白字符(空格、制表符、换行符等)[ \f\n\r\t\v]
    \S匹配任意一个非空白字符[^ \f\n\r\t\v]\[\s]
    \b匹配单词边界
    \B匹配非单词边界[^\b]
    \n匹配一个换行符
    \r匹配一个回车符
    \t匹配一个制表符
    \v匹配一个垂直制表符
    \f匹配一个换页符
    \\匹配一个反斜杠
    \0匹配一个 NULL 字符

    2 正则进阶

    元字符组合(常用)

    .*?

    .+?

    1. text1 = '<12> <1a!#e2> <>'
    2. ret1 = re.findall("<\d+>", text1) # ['<12>']
    3. ret1 = re.findall("<\w+>", text1) # ['<12>', '']
    4. ret1 = re.findall("<.+>", text1) # ['<12> <1a!#e2> <>']
    5. ret1 = re.findall("<.+?>", text1) # ['<12>', '', '', '<1a!#e2>']
    6. ret1 = re.findall("<.*?>", text1) # ['<12>', '', '', '<1a!#e2>', '<>']
    1. text2 = '''<12
    2. >
    3. yz>
    4. <1a!#
    5. e2>
    6. <>
    7. '''
    8. ret2 = re.findall("<.*?>", text2) # ['', '<>']
    9. ret2 = re.findall("<.*?>", text2, re.S) # ['<12\n>', '', '', '<1a!#\ne2>', '<>']

    模式修正符

    修正符re模块提供的变量描述
    ire.I使模式对大小写不敏感,也就是不区分大小写
    mre.M使模式在多行文本中可以多个行头和行位,影响^和$
    sre.S让通配符,可以代表所有的任意原子(包括换行符\n在内)
    1. import re
    2. ret = re.findall(
    3. '.*?(.*?).*?(.*?)',
    4. s,
    5. re.S,
    6. )
    7. print(ret)
    8. print(len(ret))

     re模块的方法

    re.Match对象对应两个方法
    ret.span()  返回符合规则的字符串的出现位置,为元组
    ret.group()  返回第一个匹配字符串

    函数描述
    findall按指定的正则模式查找文本中符合正则模式的匹配项,以列表格式返回结果
    search在字符串的任何位置查找首个符合正则模式的匹配项,存在返回re.Match对象,不存在返回None
    match判定字符串开始位置是否匹配正则模式的规则,匹配返回re.Match对象,不匹配返回None
    split按指定的正则模式来分割字符串,返回一个分割后的列表
    sub/subn把字符串按指定的正则模式来查找符合正则模式的匹配项,并可以替换一个或多个匹配项成其他内容
    compile将规则编译,可以重复使用
    有名分组

    search和match的区别:
    search在任何位置找,match从开头找

    1. ret3 = re.search("(?P1[3-9]\d{9}).*?(?P\d+@qq\.com)", "我的手机号是18793437893,我的邮箱是123@qq.com")
    2. print(ret3.group())  # 18793437893,我的邮箱是123@qq.com
    3. print(ret3.group("tel"))  # 18793437893
    4. print(ret3.group("email"))  # 123@qq.com
    compile编译
    1. s1 = "12 apple 34 peach 21 banana"
    2. s2 = "18 apple 12 peach 33 banana"
    3. reg = re.compile(r"\d+")
    4. print(reg.findall(s1))
    5. print(reg.findall(s2))

    二、bs4

    bs4:Beautiful Soup是python的一个库,主要功能是从网页抓取数据。

    需要安装两个库
    pip install bs4
    pip install lxml

     基本使用顺序如下所示:

    1. # 调用bs4库
    2. from bs4 import BeautifulSoup
    3. # 读文件
    4. with open("第一阶段-爬虫\\JS逆向\\3-网页数据解析\\bs4\\demo.html", "r", encoding="utf-8") as f:
    5. s = f.read()
    6. # 创建bs4对象
    7. soup = BeautifulSoup(s, 'html.parser')
    8. # 或者使用如下方式
    9. soup = BeautifulSoup(open("第一阶段-爬虫\\JS逆向\\3-网页数据解析\\bs4\\demo.html", encoding="utf-8"), "html.parser")

    1 四种对象

    BeautifulSoup

    Tag

    NavigableString

    Comment

    主要使用BeautifulSoup和Tag对象 

    1. from bs4 import BeautifulSoup
    2. with open("第一阶段-爬虫\\JS逆向\\3-网页数据解析\\bs4\\demo.html", "r", encoding="utf-8") as f:
    3. s = f.read()
    4. soup = BeautifulSoup(s, 'html.parser')
    5. # Tag查找标签
    6. # 如果查到,一定是一个Tag对象
    7. print(soup.body)
    8. print(type(soup.body))
    9. print(soup.div.a) #
    10. print(soup.div.a.name) # a
    11. print(soup.a["href"]) # https://accounts.douban.com/passport/login?source=movie
    12. print(soup.a.attrs) # {'href': 'https://accounts.douban.com/passport/login?source=movie', 'class': ['nav-login'], 'rel': ['nofollow']}
    13. # 拿取文本
    14. print(soup.a.string) # 登录/注册
    15. print(soup.a.text) # 登录/注册
    16. print({link.text:link["href"] for link in soup.find_all("a")})

    2 导航文档树

    嵌套选择
    子节点、子孙节点
    父节点、祖先节点
    兄弟节点

    导入文件并创建对象

    1. from bs4 import BeautifulSoup
    2. with open("第一阶段-爬虫\\JS逆向\\3-网页数据解析\\bs4\\demo.html", "r", encoding="utf-8") as f:
    3. s = f.read()
    4. soup = BeautifulSoup(s, 'html.parser')

    嵌套选择

    1. # 嵌套选择
    2. print(soup.head.title.text)
    3. print(soup.body.a.text)

    子节点、子孙节点

    1. # 子节点、子孙节点
    2. print(soup.p.contents) # p下所有子节点
    3. print(soup.p.children) # 得到一个迭代器,包含p下所有子节点
    4. print(soup.p.descendants) # 获取子孙节点,p下所有标签都会选择出来

    父节点、祖先节点

    1. # 父节点、祖先节点
    2. print(soup.a.parent) # 获取a标签的父节点
    3. print(soup.a.parents) # 获取a标签所有祖先节点,父亲、爷爷等等

    兄弟节点

    1. # 兄弟节点
    2. print(soup.a.next_sibling) # 下一个兄弟
    3. print(soup.a.next_sibling.next_sibling) # 下下一个兄弟,以此类推
    4. print(soup.a.previous_sibling) # 上一个兄弟
    5. print(soup.a.previous_sibling.previous_sibling) # 上上一个兄弟

    3 搜索文档树

    fand_all()
    name参数(标签名过滤):字符串、正则表达式、列表、方法
    字符串:即标签名

    关键字参数(属性过滤)

    文本参数(文本过滤)

    find()
    与find_all()的区别:find()只查找第一个
    参数完全一样

    find_parents()  找所有父亲标签
    find_parent()  找父亲标签

    优点:只包含兄弟标签
    find_next_siblings()  找下边所有兄弟标签
    find_next_sibling()  找下边一个兄弟标签
    find_previous_siblings()  找上边所有兄弟标签
    find_previous_sibling()  找上边一个兄弟标签

    find_all_next()  找下边所有相同标签
    find_next()  找下边相同标签

    这里使用find_all()方法举例,其余搜索使用方法几乎相同

    name参数

      标签名
    1. # name参数:标签名
    2. ret1 = soup.find_all(name="a") # 查找所有a标签
    3. print(ret1)
      正则表达式
    1. # name参数:正则表达式
    2. ret2 = soup.find_all(name=re.compile("^a"))
    3. print(ret2)
      列表
    1. # name参数:列表
    2. ret3 = soup.find_all(name=["a", "b"])
    3. print(ret3)
      函数(精致过滤)
    1. # name参数:函数(精致过滤)
    2. # 表示拥有class和id两个属性的标签
    3. def has_class_has_id(tag):
    4. return tag.has_attr("class") and tag.has_attr("id")
    5. print(soup.find_all(name=has_class_has_id))

    关键字参数

    1. ret4 = soup.find_all(
    2. href="https://img1.doubanio.com/cuphead/movie-static/pics/apple-touch-icon.png"
    3. )
    4. ret4 = soup.find_all(
    5. attrs={
    6. "href": "https://img1.doubanio.com/cuphead/movie-static/pics/apple-touch-icon.png"
    7. }
    8. )
    9. ret4 = soup.find_all(href=re.compile("^https://"), class_="download-android", id="id1")
    10. print(ret4)

    文本参数

    1. ret5 = soup.find_all(string="豆瓣")
    2. ret5 = soup.find_all(string=re.compile("豆瓣")) # 查找含有"豆瓣"的文本
    3. ret5 = soup.find_all(string=re.compile("豆瓣"), limit=1) # 查找含有"豆瓣"的文本,只取第一个
    4. print(ret5)

    4 CSS选择器

    与CSS中选择器用法相同
    select()方法或者在搜索中使用

    1. from lxml import etree
    2. selector = etree.HTML(源码)  # 将源码转换为能被XPath匹配的格式
    3. ret = selector.xpath(表达式)  # 返回为一列表


    例:soup.select("body a")

    1. from bs4 import BeautifulSoup
    2. import re
    3. soup = BeautifulSoup(open("第一阶段-爬虫\\JS逆向\\3-网页数据解析\\正则\\豆瓣top250.html", encoding="utf-8"), "html.parser")
    4. items = soup.find_all(class_="item")
    5. print(len(items))
    6. for item in items:
    7. title = item.find(class_="title").string
    8. rating_num = item.find(class_="rating_num").string
    9. star = item.find(class_="star").find_all("span")[-1].string
    10. print(title, rating_num, star)

    三、xpath

    xpath:一种小型的查询语言,属于lxml库模块

    使用方式:

    1. from lxml import etree
    2. selector = etree.HTML(源码)  # 将源码转换为能被XPath匹配的格式
    3. ret = selector.xpath(表达式)  # 返回为一列表

    1 路径表达式

    表达式描述实例解析
    /从根节点选取/body/div[1]选取根节点下的body下的第一个div标签
    //从匹配选择的当前节点选择文档中的节点,而不考虑它们的位置//a选取文档中所有的a标签
    ./从当前节点再次进行xpath./a选取当前节点下的所有a标签
    @选取属性//@class选取所有的class属性

    2 谓语

    是放在方括号[]里,用来查找某个特定的节点或者包含某个指定值的节点

    路径表达式结果
    /ul/li[1]选取属于ul元素的第一个li元素
    /ul/li[last()]选取属于ul元素的最后一个li元素
    /ul/li[last-1]选取属于ul元素的倒数第二个li元素
    /ul/li[position()<3]选取最前面的两个属于ul元素的子元素的li元素
    //a[@title]选取所有拥有名为title属性的a元素
    //a[@title='xx']选取所有拥有title属性,并且属性值为'xx'的a元素
    //a[@title>10] > < >= <= !=选取所有拥有title属性,并且属性值为大于10的a元素
    /body/div[@price>35.00]选取body下price元素值大于35的div节点

    3 通配符

    xpath的通配符可以选取未知节点

    通配符描述
    *匹配任何元素节点
    @*匹配任何属性节点
    node()匹配任何类型节点

    4 其他用法

    表达式

    结果

    //xx[@id="" and @class=""]获取满足两者要求的元素
    //xx | //xx获取若干路径的元素
    //xx[id/@class=""]获取xx标签中包含或者对应id/class的元素
    //xx[n]获取xx标签的第n个元素,索引从1开始,last()获取最后一个
    //xx[starts-with(@id, "ll")]获取xx标签中id属性中以ll开头的,class类似
    //xx[contains(@id, "ll")]获取xx标签中id属性中包含ll的,class类似
    //xx/text()获取xx标签中的文本值
    //div/a/@href获取a标签中的href属性值
    //*获取所有,例//*[@class="xx"]:获取所有class为xx的标签

     获取节点内容转换成字符串

    1. c = tree.xpath('//li/a')[0]
    2. result = etree.tostring(c, encoding='utf-8')
    3. print(result.decode('utf-8'))

    5 文件操作

    文件句柄

    open()方法

    mode参数为对文件的操作,r只读,w只写,rw读写...

    encoding表示编码方式

    绝对路径

    1. filer = open(
    2. "D:/桌面/Python自学/第一阶段-爬虫/JS逆向/3-网页数据解析/xpath/豆瓣top250.html",
    3. mode="r",
    4. encoding="utf-8",
    5. )

    相对路径

    file = open("/豆瓣top250.html", mode="r",encoding="utf-8",)

    读操作

    方法功能
    file.read()直接获取文件内容,参数为整型数字,光标按字移动
    file.readline()按行获取文件内容,参数为整型数字,光标按字移动
    file.readlines()获取文件所有行,放在列表中,参数为整型数字,光标按行移动

    通常以以下方式循环高效获取数据:

    1. for line in file:
    2. print(line)

    写操作 

    覆盖写

    会将源文件内容清空再存入

    1. filew = open(
    2. "write.txt",
    3. mode="w",
    4. encoding="utf-8",
    5. )
    6. filew.write("")
    7. filew.close()
    '
    运行
    追加写
    1. filew = open(
    2. "write.txt",
    3. mode="a",
    4. encoding="utf-8",
    5. )
    6. filew.write("")
    7. filew.close()
    '
    运行
  • 相关阅读:
    【第十九篇】- Maven NetBeans
    【SSM】SpringMVC系列——SpringMVC概述
    带你掌握Mysql中的各种锁
    大数据Hadoop之——Hadoop 3.3.4 HA(高可用)原理与实现(QJM)
    Linux开发工具:vim的介绍和用法及其简单配置
    Linux YUM源(本地/网络源)配置详解
    修复YOLOFacePose中存在关键点异常的问题
    多进程启动不同的python程序
    20231014后台面经总结
    java面试注意要点
  • 原文地址:https://blog.csdn.net/qq_63043783/article/details/139374233