Python网络爬虫(数据解析-xpath模块)
在xpath定位时,如果定位的xpath路径中存在tbody标签时,需跳过tbody标签,否则不能拿到结果(例如:./tr/div/tbody/span/a/text() --> 省略tbody即可 --> ./tr/div//span/text() )
一、实现数据爬取的流程
- 指定url
- 基于requests模块发起请求
- 获取响应对象中的数据
- 数据解析
- 进行持久化存储
在持久化存储之前需要进行指定数据解析。因为大多数情况下的需求,我们都会指定去使用聚焦爬虫,也就是爬取页面中指定部分的数据值,而不是整个页面的数据。
二、模块安装
# 安装lxml pip install lxml
# 创建一个etree对象
from lxml import etree
tree = etree.parse(fileName) # fileName 是一个本地的html文件
tree = etree.HTML(page_text) # page_text 是网页获取的str类型字符串
三、常用xpath语法
测试页面,test.html
<html lang="en"> <head> <meta charset="UTF-8" /> <title>测试bs4</title> </head> <body> <div> <p>百里守约</p> </div> <div class="song"> <p>李清照</p> <p>王安石</p> <p>苏轼</p> <p>柳宗元</p> <a href="http://www.song.com/" title="赵匡胤" target="_self"> <span>this is span</span> 宋朝是最强大的王朝,不是军队的强大,而是经济很强大,国民都很有钱</a> <a href="" class="du">总为浮云能蔽日,长安不见使人愁</a> <img src="http://www.baidu.com/meinv.jpg" alt="" /> </div> <div class="tang"> <ul> <li><a href="http://www.baidu.com" title="qing">清明时节雨纷纷,路上行人欲断魂,借问酒家何处有,牧童遥指杏花村</a></li> <li><a href="http://www.163.com" title="qin">秦时明月汉时关,万里长征人未还,但使龙城飞将在,不教胡马度阴山</a></li> <li><a href="http://www.126.com" alt="qi">岐王宅里寻常见,崔九堂前几度闻,正是江南好风景,落花时节又逢君</a></li> <li><a href="http://www.sina.com" class="du">杜甫</a></li> <li><a href="http://www.dudu.com" class="du">杜牧</a></li> <li><b>杜小月</b></li> <li><i>度蜜月</i></li> <li><a href="http://www.haha.com" id="feng">凤凰台上凤凰游,凤去台空江自流,吴宫花草埋幽径,晋代衣冠成古丘</a></li> </ul> </div> </body> </html>
常见用法:
注意:
- xpath方法返回值永远是一个列表
- 索引定位中:索引值是从1开始
基于标签定位:
tree.xpath('/html/head/meta')
tree.xpath('//meta')
xpath表达式中最左侧的/和//的区别是什么?
- /表示我们必须从根标签进行定位
- //表示我们可以从任意位置标签定位
属性定位: #找到class属性值为song的div标签 //div[@class="song"]
层级&索引定位: #找到class属性值为tang的div的直系子标签ul下的第二个子标签li下的直系子标签a //div[@class="tang"]/ul/li[2]/a
tree.xpath('//div[@class="tang"]/ul/li[3]')
tree.xpath('//div[@class="tang"]//li[3]') # 与上一条属性索取为相同结果
在xpath表达式中非最左侧的/和//的区别?
- /表示一个层级
- //表示多个层级
逻辑运算: #找到href属性值为空且class属性值为du的a标签 //a[@href="" and @class="du"]
模糊匹配: //div[contains(@class, "ng")] //div[starts-with(@class, "ta")]
取文本: # /表示获取某个标签下的文本内容 # //表示获取某个标签下的文本内容和所有子标签下的文本内容 //div[@class="song"]/p[1]/text() //div[@class="tang"]//text()
取属性: //div[@class="tang"]//li[2]/a/@href
四、项目实战(好段子网站)
项目需求:获取好段子中段子的内容和作者 http://www.haoduanzi.com
from lxml import etree import requests url='http://www.haoduanzi.com/category-10_2.html' headers = { 'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.181 Safari/537.36', }
url_content=requests.get(url,headers=headers).text #使用xpath对url_conten进行解析 #使用xpath解析从网络上获取的数据 tree=etree.HTML(url_content) #解析获取当页所有段子的标题 title_list=tree.xpath('//div[@class="log cate10 auth1"]/h3/a/text()') ele_div_list=tree.xpath('//div[@class="log cate10 auth1"]') text_list=[] #最终会存储12个段子的文本内容 for ele in ele_div_list: #段子的文本内容(是存放在list列表中) text_list=ele.xpath('./div[@class="cont"]//text()') #list列表中的文本内容全部提取到一个字符串中 text_str=str(text_list) #字符串形式的文本内容防止到all_text列表中 text_list.append(text_str)
print(title_list)
五、项目实战(煎蛋网)
需求:下载煎蛋网中的图片数据:http://jandan.net/ooxx
import requests from lxml import etree from fake_useragent import UserAgent import base64 import urllib.request url = 'http://jandan.net/ooxx' ua = UserAgent(verify_ssl=False,use_cache_server=False).random headers = { 'User-Agent':ua } page_text = requests.get(url=url,headers=headers).text #查看页面源码:发现所有图片的src值都是一样的。 #简单观察会发现每张图片加载都是通过jandan_load_img(this)这个js函数实现的。 #在该函数后面还有一个class值为img-hash的标签,里面存储的是一组hash值,该值就是加密后的img地址 #加密就是通过js函数实现的,所以分析js函数,获知加密方式,然后进行解密。 #通过抓包工具抓取起始url的数据包,在数据包中全局搜索js函数名(jandan_load_img),然后分析该函数实现加密的方式。 #在该js函数中发现有一个方法调用,该方法就是加密方式,对该方法进行搜索 #搜索到的方法中会发现base64和md5等字样,md5是不可逆的所以优先考虑使用base64解密 #print(page_text) tree = etree.HTML(page_text) #在抓包工具的数据包响应对象对应的页面中进行xpath的编写,而不是在浏览器页面中。 #获取了加密的图片url数据 imgCode_list = tree.xpath('//span[@class="img-hash"]/text()') imgUrl_list = [] for url in imgCode_list: #base64.b64decode(url)为byte类型,需要转成str img_url = 'http:'+base64.b64decode(url).decode() imgUrl_list.append(img_url) for url in imgUrl_list: filePath = url.split('/')[-1] urllib.request.urlretrieve(url=url,filename=filePath) print(filePath+'下载成功')
import requests from lxml import etree headers = { "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3573.0 Safari/537.36" } url = "http://jandan.net/ooxx/page-%d" for page in range(1, 5): new_url = format(url%page) page_text = requests.get(url=new_url, headers=headers).text tree = etree.HTML(page_text) li_list = tree.xpath("//ol[@class='commentlist']/li") for li in li_list: # 获取文章标题 title = li.xpath(".//div[@class='author']/strong/text()") # 图片地址 img_url = "http:"+li.xpath(".//div[@class='text']/p/img/@src")[0] print(title, img_url) break
六、项目实战(糗事百科)
项目需求:获取糗事百科标题与文章内容 ,网址:url:https://www.qiushibaike.com/text/page/1/
import requests from lxml import etree headers = { "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3573.0 Safari/537.36" } url = "https://www.qiushibaike.com/text/page/%d/" # 爬取前5页内容 for page in range(1, 5): new_url = format(url%page) page_text = requests.get(url=new_url, headers=headers).text # 生成etree对象 tree = etree.HTML(page_text) # 获取所有li标签 div_list = tree.xpath("//div[@class='col1']/div") for div in div_list: # | 管道符 满足其中一个条件即可爬取 title = div.xpath("./div/a[2]/h2/text() | ./div/span[2]/h2/text()")[0] # 爬取用户名 # 爬取内容详情 content = div.xpath("./a[@class='contentHerf']//span/text()") print(title, content)
七、项目实战(BOOS直聘)
- 需求:爬取boss中的岗位信息(岗位名称,薪资,公司名称,岗位描述)
- url:'https://www.zhipin.com/c101010100/?query=python爬虫&page=%d'
import requests from lxml import etree headers = { "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3573.0 Safari/537.36" } url = "https://www.zhipin.com/c101010100/?query=python爬虫page=%d" for page in range(1, 3): new_url = format(url%page) page_text = requests.get(url=new_url, headers=headers).text # 生成etree对象 tree = etree.HTML(page_text) # 获取页面中所有li标签 li_list = tree.xpath("//div[@class='job-list']/ul/li") fp = open("boos.txt", "a", encoding="utf-8") for li in li_list: # 职位标题 job_title = li.xpath(".//a/div[1]/text()")[0] # 薪资 salary = li.xpath(".//a/span/text()")[0] # 职位详情页url detail_url = "https://www.zhipin.com"+li.xpath(".//a/@href")[0] # 公司名字 company_title = li.xpath(".//div[@class='company-text']//a/text()")[0] # 职位详情页 detail_page_text = requests.get(url=detail_url, headers=headers).text detail_tree = etree.HTML(detail_page_text) # 职位描述详情 job_desc = detail_tree.xpath("//div[@class='job-sec']/div/text()") job_desc = "".join(job_desc) # 文件保存 fp.write(job_title+":"+salary+":"+company_title+":"+job_desc+"\n\n\n") print(job_title, salary, company_title) print(job_desc) print(company_title," 公司数据下载完成!!!") fp.close()
八、项目实战(彼岸图网,局部中文乱码处理)
-
特例:处理中文乱码
import requests from lxml import etree headers = { "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3573.0 Safari/537.36" } url = "http://pic.netbian.com/4kmeinv/index_%d.html" for page in range(1, 3): if page == 1: new_url = "http://pic.netbian.com/4kmeinv/index.html" new_url = format(url%page) # 返回请求状态 200 OK response = requests.get(url=new_url, headers=headers) # 在全局处理中文乱码,开销大 #response.encoding = "gbk" # 方式一 #response.encode("iso-8859-1").decode("gbk") # 方式二 # 获取网页详情 page_text = response.text # 生成etree对象 tree = etree.HTML(page_text) # 获取所有的li标签 li_list = tree.xpath("//ul[@class='clearfix']/li") for li in li_list: # 获取图片url img_url = "http://pic.netbian.com"+li.xpath("./a/img/@src")[0] # 图片人物的名字 img_name = li.xpath("./a/b/text()")[0] # 局部处理中文乱码,推荐 img_name = img_name.encode("iso-8859-1").decode("gbk") print(img_url, img_name) break
九、项目实战(xpath管段符)
需求:爬取所有的城市名称 url:https://www.aqistudy.cn/historydata/
import requests from lxml import etree headers = { "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3573.0 Safari/537.36" } url = "https://www.aqistudy.cn/historydata/" page_text = requests.get(url=url, headers=headers).text tree = etree.HTML(page_text) # 方式一 单独获取热门城市 & 所有城市名称 hot_cities = tree.xpath("//div[@class='hot']//li/a/text()") all_cities = tree.xpath("//div[@class='all']//li/a/text()") print(hot_cities, all_cities) # 方式二 通过管道符 获取热门城市名称+所有城市名称 citys = tree.xpath("//div[@class='hot']//li/a/text() | //div[@class='all']//li/a/text()") print(citys)
十、补充(scrapy框架中的xpath语法)
scrapy中的xpath语法:
response.xpah(语法)返回的是一个选择器对象,需要通过extract()提取
extract() -- > 提取结果的是列表对象(复数)
extract_first() --> 提取结果是一个字符串,是对列表中的第一个selector对象进行提取
普通xpath语法:
返回的是一个列表或者字符串
scrapy爬虫文件示例:
import scrapy from ..items import ProxiaohuawangItem class FirstSpider(scrapy.Spider): # 应用名字,爬虫的唯一标识 name = 'first' # 允许访问的域名, 推荐不使用 # allowed_domains = ['www.xxx.com'] # 爬虫访问的url列表 start_urls = ['http://www.xiaohuar.com/hua/'] # 数据解析 def parse(self, response): div_list = response.xpath('//*[@id="list_img"]/div/div[1]/div') for div in div_list: # name = div.xpath('./div//span/a/text()')[0].extract() # 提取方式一 name = div.xpath('./div//span/a/text()').extract_first() # 提取方式二 img_src = div.xpath('./div//a/img/@src').extract_first()
# 假设示例
#content = div.xpath('./div/div//text()').extract() # 提取div下的所有文本内容,是一个列表 #content = "".join(content) # 列表转换成字符串
# 实例化一个FirstbloodItem对象 item = ProxiaohuawangItem() item["name"] = name item["img_src"] = "http://www.xiaohuar.com"+img_src # 每次返回一个item对象提交到pipeline yield item
https://www.cnblogs.com/WiseAdministrator/