CSS选择器
html中为指定元素指定显示效果,比如颜色,背景,字体等不同的属性,这些样式都是通过css选择器告诉浏览器指定样式风格。
表达式 | 含义 |
---|---|
#animal | 获取id为animal的所有元素 |
.animal | 获取class为animal的所有元素 |
a.active | 获取类为active的a标签 |
.animal > .pig | 获取类animal直接子元素中类为.pig的元素 |
.animal .pig | 获取类animal后代元素中类为.pig的元素 |
a[href*=“animal”] | 获取包含类animal的a元素 |
a[href^=“http”] | 获取href以http开头的a元素 |
a[href$=“gov.cn”] | 获取href以gov.cn结尾的a元素 |
div[class=“animal”][ctype=“pig”] | 获取多属性同时具备的元素 |
div > a:nth-child(2) | 获取div下的第二个a元素 |
.pig , .animal | 同时选择两个class的所有元素 |
p:nth-last-child(1) | 获取倒数第一个p元素 |
p:nth-child(even) p:nth-child(odd) | 获取奇数偶数节点 |
h3 + span | 获取h3 后面紧跟着的兄弟节点 span |
h3 ~ span | 获取h3 后面所有的兄弟节点 span |
实战
链家
目标抓取网站:https://su.lianjia.com/ershoufang/pg
抓取内容:分页抓取二手房的标题,地址,信息,关注量,标签,总价,单价等
分析
通过获取网页源代码发现所有的二手房信息都直接渲染在页面上,那么可以直接请求页面地址分析二手房源码后,通过parsel库parsel.Selector(html_data)
转为我们可以使用选择器分析的对象。
通过css选择器.clear.LOGCLICKDATA
拿到所有的二手房信息所在的li元素
在li元素下可以css选择器获取所有的.title a::text
标题,.positionInfo a::text
地址,.followInfo::text
关注量等信息。
selector = parsel.Selector(html_data)
lis = selector.css('.clear.LOGCLICKDATA')
for li in lis:
title = li.css('.title a::text').get() # 标题
address = li.css('.positionInfo a::text').getall() # 地址
address = ','.join(address)
houseInfo = li.css('.houseInfo::text').get() # 信息
followInfo = li.css('.followInfo::text').get() # 关注
tags = li.css('.tag span::text').get() # 标签
tags = ','.join(tags)
totalPrice = li.css('.totalPrice span::text').get() + '万' # 总价
unitePrice = li.css('.unitPrice span::text').get() # 单价
title_url = li.css('.title a::attr(href)').get() # 标题
print(title, address, houseInfo, followInfo, tags, totalPrice, unitePrice, title_url, sep="---")
爬取完成
点击下一页的时候,页面url添加了路径参数pg{},那么可以通过加该字段实现分页抓取。
猫眼电影
分析
目标抓取网站:https://maoyan.com/board
抓取内容:热映口碑榜的电影名,主演,上映时间等。
老规矩,查看网页源代码电影数据完整返回给前端,没有做异步请求。那么直接访问猫眼的热映口碑榜通过parsel库解析成Selector对象,开始利用css选择器分析页面字段。
通过控制台源码发现类.board-wrapper下dd元素包含了所有的电影信息,那么遍历其下的标签列表根据css选择器筛选拿到需要的数据即可。
selector = parsel.Selector(html_data)
print(selector)
dds = selector.css('.board-wrapper dd')
for dd in dds:
title = dd.css('.name a::attr(title)').get()
star = dd.css('.star::text').get().strip()
releasetime = dd.css('.releasetime::text').get()
score = dd.css('.score i::text').getall()
score = ''.join(score)
print(title, star, releasetime, score)
with open('maoyan.csv', mode='a', encoding='utf-8', newline='') as f:
csv_write = csv.writer(f)
csv_write.writerow([title, star, releasetime, score])
爬取完成
喜马拉雅
分析
目标网站:https://www.ximalaya.com/xiangsheng/9723091
抓取内容:下载当前主题的所有页面的音频文件。
老规矩,查看网页源代码发现所有的音频标签会在当前页面ur后添加音频的id跳转到一个新的页面,如:https://www.ximalaya.com/xiangsheng/9723091/45982355
点击播放后,控制台的Media出现请求的音频地址,如:https://aod.cos.tx.xmcdn.com/group31/M01/36/04/wKgJSVmC6drBDNayAh_Q8WincwI414.m4a
通过控制台搜索音频关键字段,找到返回音频地址的请求https://www.ximalaya.com/revision/play/v1/audio?id=46106992&ptype=1
该请求参数由音频id和ptype=1组成,通过css选择器.sound-list li.lF_ a::attr(href)
分析列表页的音频的href拿到音频id,通过css选择器.sound-list li.lF_ a::attr(title)
拿到音频标题。点击下一页发现只是在原url后添加p{page}字段,综上通过open函数写入音频文件完成下载。
titles = selector.css('.sound-list li.lF_ a::attr(title)').getall()
href = selector.css('.sound-list li.lF_ a::attr(href)').getall()
# zip() 可以讲两个列表进行打包, 遍历之后 是一个元组
data = zip(titles, href)
for index in data:
title = index[0]
mp3_id = index[1].split('/')[-1]
# f'{mp3_id}' '{}'.format(mp3_id) 字符串格式化方法
index_url = f'https://www.ximalaya.com/revision/play/v1/audio?id={mp3_id}&ptype=1'
response_1 = requests.get(url=index_url, headers=headers)
# 什么是json数据 字典嵌套字典 还嵌套一些列表
# json数据取值和字典取值方式是一样的 根据关键词提取内容 通俗的讲 就是根据左边的内容提取右边的内容
# print(response_1.text)
mp3_url = response_1.json()['data']['src']
print(title, mp3_url)
# 保存数据
# 保存数据: 如果是图片/音频/视频 等 都是要获取它的二进制数据,要以二进制的数据保存
mp3_content = requests.get(url=mp3_url).content
# 相对路径
with open('相声\\' + title + '.mp3', mode='wb') as f:
f.write(mp3_content)
print('正在保存: ', title)
爬取完成
XPATH选择器
XPath (XML Path Language) 是由国际标准化组织W3C指定的,用来在 XML 和 HTML 文档中选择节点的语言。目前主流浏览器 (chrome、firefox,edge,safari) 都支持XPath语法,xpath有 1 和 2 两个版本,目前浏览器支持的是 xpath 1的语法,且比CSS选择器功能更强大。
表达式 | 含义 |
---|---|
/html/body/div | 选择根节点html下面的body下面的div元素,/从子节点找,//从所有子节点包括子节点的子节点中找 |
//div/* | 所有div节点下所有元素 |
//*[@id=‘west’] | id为west的元素 |
//select[@class=‘single_choice’] | class为single_choice的select元素 |
//p[@class=“capital huge-city”] | 多元素组合选择 |
//*[@multiple] | 具有multiple属性的元素 |
//*[contains(@style,‘color’)] | style包含color的元素 |
//*[starts-with(@style,‘color’)] | 以style是color开头的元素,//*[ends-with(@style,‘color’)]结尾元素 |
//div/p[2] | 所有div下的第二个p标签 |
//p[last()] | 最后一个p元素 |
//div/p[last()-2] | 所有div下倒数第三个p元素 |
//option[position()<=2] | option类型的第1-2个元素 |
//*[@class=‘multi_choice’]/*[position()>=last()-2] | 选择class属性为multi_choice的后3个子元素 |
//option|//h4 | 所有的option元素 和所有的 h4 元素 |
//*[@id=‘china’]/… | 选择 id 为 china 的节点的父节点 |
//*[@id=‘china’]/…/…/… | 上上父节点 |
//*[@class=‘single_choice’]/following-sibling::div | 选择后续节点中的div节点 等同于CSS选择器.single_choice ~ * |
//*[@class=‘single_choice’]/preceding-sibling::div | 前面兄弟节点 |
实战
新笔趣阁
分析
目标网站:http://www.xbiquge.la/10/10489/
抓取内容:抓取三寸人间所有章节的文章保存。
章节列表只有小说章节信息,点击每个章节跳转到章节页面,通常xpath表达式//div[@id="info"]/h1/text()
拿到书籍名称,所有的章节都依赖于于id为list的div下的dl下的dd下的a标签的href属性跳转到章节页面。
拼接主域名http://www.xbiquge.la
即可跳转到章节详情页面,通过xpath表达式//*[@id="content"]/text()
拿到详情页面小说的完整内容
# 开文件流 打开一个文件 把我们数据写入到文件中去 a是追加写入 写入完第一章就继续追加写入第二章
with open(book_name + '.txt', 'a', encoding='utf-8')as f:
f.write(book_name+'\n')
# title 章节的名称 urls 每个章节的详情链接
# 遍历获取到该本书的每个章节和对应的内容详情链接 zip一次性遍历多个列表
for title,urls in zip(book_title,book_url):
c_url='http://www.xbiquge.la'+urls
print(title)
print(c_url)
# 异常处理
try: #捕捉异常
#参数1:单个章节的url:以获取到这个章节的小说内容的html源码 参数2:headers 参数3:请求等待时间3秒
titles_url = requests.get(c_url, headers=headers, timeout=3).content.decode('utf-8')
except: # 如果捕捉异常怎么办 请求失败那就再请求一遍
titles_url = requests.get(c_url, headers=headers).content.decode('utf-8')
# 那我们还差一个小说文本内容对不对 那每个章节链接我们有了
# 每个章节里面的内容是不是好解决 一样xpath语法给他获取下来
# 通过xpath获取到小说文本内容
book_content = etree.HTML(titles_url).xpath('//*[@id="content"]/text()')
f.write(title) # 先写入章节名称
f.write('\n')
# f.write不能够写列表,但可以写字符串格式(二进制)。。。 所以要for循环
for line in book_content:
f.write(line) # 再写入章节对应的内容
f.write('\n') # 每写完一章换行 一共1000多个章节
爬取完成
其实很多情况下不需要自己去分析dom节点定位css或xpath表达式,chrome已经为我们集成了插件。
JSON
很多情况页面不直接返回html或xml文本元素,或者这些文本分析起来很困难的情况下,可以通过控制台中的xhr模式抓取后端请求回来的json数据,直接解析json即可拿到想要的数据。
实战
拉勾
分析
目标网站:https://www.lagou.com/jobs/list_C%2B%2B?labelWords=&fromSearch=true&suginput=
抓取内容:抓取首页职位地址,公司名,规模等信息保存。
搜索C++后,打开控制台将结果中的带薪年假
搜索拿到实际请求路径https://www.lagou.com/jobs/positionAjax.json?needAddtionalResult=false
,该请求是post请求,参数如下
data = {
"first": "true",
"pn": "1",
"kd": "C++"
}
通过控制台Preview分析返回的json数据,data['content']['positionResult']['result']
即为职位信息
不过当我们直接请求时会报dtacess deny,可能对请求头中的参数做了校验。
Traceback (most recent call last):
File "F:/MyProject/CrawlerBase/lagou/lagou.py", line 21, in <module>
result = data['content']['positionResult']['result']
KeyError: 'content'
{'clientIp': '61.155.198.*',
'msg': 'dtaccess deny ',
'state': 2410,
'status': False}
我们将Cookie和User-Agent加入header后,即可以完整请求到json数据,进行数据分析。
resp = requests.post(api_url, headers=headers)
pprint(resp.json())
data = resp.json()
result = data['content']['positionResult']['result']
# [print(r) for r in result]
for r in result:
d = {
'city': r['city'],
'companyFullName': r['companyFullName'],
'companySize': r['companySize'],
'education': r['education'],
'positionName': r['positionName'],
'salary': r['salary'],
'workYear': r['workYear']
}
with open('拉钩职位.csv',mode='a',encoding='utf-8') as f:
f.write(",".join(d.values()))
f.write("\n")
爬取完成
完整源码请关注微信公众号:ReverseCode,回复:爬虫基础