写在前面的话:切记切记要设置休眠,不能频繁请求
小白的一点案例记录,望大神们手下留情。。。
共两部分源码分别见3.1和3.2
一、背景前提
日常辛苦工(mo)作(yu)之后的某时,心血来潮想查下以前离职公司现在怎么样了,于是各种企业信息查询,某查查登场,注册–>验证–>绑定–>登录,还好可以看了,猛然眼前一亮,诉讼异常,悲(窃)伤(喜)着点开,会员,看不到全部内容,咱也理解,毕竟人家是公司不是盈利机构,于是乎,就有了本文的初始念头:我自己取数据自己查找自己分析!
二、准备工作
再叨叨一句:小心,爬着爬着就进去了!
用selenium模拟比较真实一些,慢就慢点无所谓
环境:win10、python3.7
工具:anaconda spyder、chrome driver
三方包:selenium、pandas、bs4、requests、random
三、数据采集及清洗
分析了下网站结构,
1、首页的搜索按钮必须输入关键词才能搜索,不同关键词的搜索结果数量不一;
2、搜索“0”出现的案例条数,与首页下方的案例点击后相加得到的条数一致;
3、列表页的标题行固定为class="fd-list-01"
;
4、标题页没有进入详情页的连接;且点击标题后,新窗口打开详情页;
5、打开详情页后发现,详情页连接较统一,拼接变量为文章类型和文章ID;
6、列表页标题中的onclick即有此两个变量;
7、测试验证上述5和6成功;
8、凑个数吧;
1.目录采集
先上源码:
# -*- coding: utf-8 -*-
""" Created on Thu Sep 24 16:52:53 2020 @author: janlyn """
from selenium import webdriver
import time
import pandas as pd
import random
driver = webdriver.Chrome()
driver.get(图片)
driver.find_element_by_id('fd-search').send_keys('0')
driver.find_element_by_class_name('fd-btn').click()
driver.switch_to.window(driver.window_handles[1])
# 获取列表页信息
def get_data():
items = driver.find_elements_by_class_name('fd-list-01')
for ele in items[1:]:
titles = ele.text
a = ele.find_element_by_tag_name('a')
# 获取onlick中的内容
alls = a.get_attribute('onclick')
if alls:
# 提取onlick中的内容,完整内容为
# οnclick="ckxq('开庭公告','D5EA00BE1700316F2AA7B7C5EEF535F5')"
li = alls.replace("ckxq('","").replace("')","").split("','")
li.append(titles)
al.append(li)
# 点击下一页
def click_next():
pages = driver.find_element_by_class_name('pageBtnWrap').find_elements_by_tag_name('a')
for page in pages:
title = page.get_attribute('title')
if title == '下一页':
page.click()
# 若失败则重新执行当前页
def loop_temp(func,page):
reg = 1
while reg == 1:
try:
func
reg = 0
print('{}执行成功'.format(page))
except:
print('{}执行失败,再来一次'.format(page))
reg = 1
# 字段头,type和id需要构造详情页url,title只是顺手取出来,方便验证
al = [['type','id','title']]
# 循环遍历全部页数
for i in range(1,28):
loop_temp(get_data(),i)
reg = 1
# 判断获取的数据是否为当前遍历页数据
while reg == 1:
loop_temp(click_next(),i)
time.sleep(random.randrange(50,150,10)/100)
curr = driver.find_element_by_class_name('curr').text
if curr == str(i+1):
reg = 0
# 最后一页直接获取数据
get_data()
# 转为df数据框,方便操作
df_te = pd.DataFrame(te[1:],columns=te[0])
# 去重数据
df_te_tmp = df_te.drop_duplicates()
# 保存列表数据
df_te_tmp.to_excel('spyder.xlsx',index=False)
直接保存运行肯定会出错滴,你得调试呐,这又不是成品源码;
思路如下:
1.0实例化浏览器
driver = webdriver.Chrome()
1.1打开网站
.get()
方法打开链接
driver.get(图片)
1.2输入关键词
.send_keys()
方法输入内容
driver.find_element_by_id('fd-search').send_keys('0')
1.3点击检索
.click()
方法进行点击
driver.find_element_by_class_name('fd-btn').click()
1.4切换窗口
可以通过driver.title
查看当前tab标题
可以通过driver.refresh()
刷新当前tab
driver.window_handles
获取tab句柄
.switch_to.window()
根据句柄进行切换
driver.switch_to.window(driver.window_handles[1])
1.5获取列表页数据
.get_attribute()
可获取当前元素中的指定属性值,前文述的类型和id均可在’onlick’中进行获取
# 获取列表页信息
def get_data():
items = driver.find_elements_by_class_name('fd-list-01')
for ele in items[1:]:
titles = ele.text
a = ele.find_element_by_tag_name('a')
# 获取onlick中的内容
alls = a.get_attribute('onclick')
if alls:
# 提取onlick中的内容,完整内容为
# οnclick="ckxq('开庭公告','D5EA00BE1700316F2AA7B7C5EEF535F5')"
li = alls.replace("ckxq('","").replace("')","").split("','")
li.append(titles)
al.append(li)
1.6点击下一页
循环class
为pageBtnWrap
的a
标签,并判断title
的值是否为下一页
,若是则点击
# 点击下一页
def click_next():
pages = driver.find_element_by_class_name('pageBtnWrap').find_elements_by_tag_name('a')
for page in pages:
title = page.get_attribute('title')
if title == '下一页':
page.click()
1.7列表页结束
点击下一页或获取列表页失败时,需要重新尝试
# 若失败则重新执行当前页
def loop_temp(func,page):
reg = 1
while reg == 1:
try:
func
reg = 0
print('{}执行成功'.format(page))
except:
print('{}执行失败,再来一次'.format(page))
reg = 1
已知失败原因如下:
1)点击下一页后实际未点击(可能页面未加载完成导致),导致重复采集;
2)点击下一页后,页面未加载完毕,导致页面属性缺失,从而获取不到列表页相关信息;
3)点击过快导致页面加载失败;
pd可以直接将二维数组转化为数据框,故将提取的数据存为列表,先上字段表头
# 字段头,type和id需要构造详情页url,title只是顺手取出来,方便验证
al = [['type','id','title']]
并循环遍历每一页进行采集
最后一页时多调用一次列表数据,而不需要进入循环
# 循环遍历全部页数
for i in range(1,28):
loop_temp(get_data(),i)
reg = 1
# 判断获取的数据是否为当前遍历页数据
while reg == 1:
loop_temp(click_next(),i)
time.sleep(random.randrange(50,150,10)/100)
curr = driver.find_element_by_class_name('curr').text
if curr == str(i+1):
reg = 0
# 最后一页直接获取数据
get_data()
# 转为df数据框,方便操作
df_te = pd.DataFrame(te[1:],columns=te[0])
# 去重数据
df_te_tmp = df_te.drop_duplicates()
# 保存列表数据,到当前工作目录下
df_te_tmp.to_excel('spyder.xlsx',index=False)
2.详情页采集
上源码
在这里插入代码片
2.1读取列表页采集结果
detail = pd.read_excel('spyder.xlsx')
2.2构造url
type_dic = { 图片}
url = type_dic[detail_type]
url = '{}?id={}'.format(url,detail_id)
2.3请求链接并清洗数据
2.3.1方法一:requests模拟请求头获取(不推荐)
此处有个工具,详见:curl转python
def get_detail_spyder(detail_type = '案例',detail_id = 'ff8080815a5ef25f015a7407287c1b3a',title='列表标题'):
# BeautifulSoup 解析
headers = {
'Connection': 'keep-alive',
'Cache-Control': 'max-age=0',
'Upgrade-Insecure-Requests': '1',
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.89 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',
'Sec-Fetch-Site': 'same-origin',
'Sec-Fetch-Mode': 'navigate',
'Sec-Fetch-User': '?1',
'Sec-Fetch-Dest': 'document',
'Referer': 图片,
'Accept-Language': 'zh-CN,zh;q=0.9',
}
# params = (('id', detail_id),)
url = type_dic[detail_type]
url = '{}?id={}'.format(url,detail_id)
try:
# 随机休眠0.5-1.5秒
time.sleep(random.randrange(50,150,10)/100)
# response = requests.get(url, headers=headers, params=params)
response = requests.get(url, headers=headers)
bsobj = BeautifulSoup(response.text,'lxml')
# 标题正文等合体
bs_all = bsobj.find(attrs={ "class":"fd-fix"})
# 文章标题
title_name = bs_all.findChild().text
# 文章标题下方的文章信息,待解析
title_info = bs_all.findChild().findNext().text
fabudanwei,suoshushengfen,fabushijian,liulanliang=('','','','')
# 有的文章无该4个字段中的某个,故循环遍历匹配提取
for i in title_info.split('\u3000\u3000'):
if '发布单位' in i:
fabudanwei = i.split(':')[1]
elif '所属省份' in i:
suoshushengfen = i.split(':')[1]
elif '发布时间' in i:
fabushijian = i.split(':')[1]
elif '浏览量' in i:
liulanliang = i.split(':')[1]
zhengwen_html = str(bs_all.find(attrs={ "class":"fd-alt-all"}))
# 清除style标签
[s.extract() for s in bs_all("style")]
zhengwen = bs_all.find(attrs={ "class":"fd-alt-all"}).text
# 构造并返回字典
dic = { '类型':detail_type,'标题':title_name,'列表标题':title,'信息':title_info,\
'发布单位':fabudanwei,'所属省份':suoshushengfen,'发布时间':fabushijian,\
'浏览量':liulanliang,'正文html':zhengwen_html,'正文':zhengwen,'url':url}
return dic
except Exception as e:
print(url,e)
return 0
2.3.2方法二:selenium模拟浏览器(推荐)
def get_detail(driver,detail_type = '案例',detail_id = 'ff8080815a5ef25f015a7407287c1b3a',title='列表标题'):
# selenium解析
url = type_dic[detail_type]
url = '{}?id={}'.format(url,detail_id)
driver.get(url)
# 随机休眠0.5-1.5秒
time.sleep(random.randrange(50,150,10)/100)
try:
# 标题正文等合体
bs_all = driver.find_element_by_class_name('fd-fix')
# 文章标题
title_name = bs_all.find_element_by_tag_name('h2').text
# 文章标题下方的文章信息,待解析
title_info = bs_all.find_element_by_tag_name('h5').text
fabudanwei,suoshushengfen,fabushijian,liulanliang=('','','','')
# 有的文章无该4个字段中的某个,故循环遍历匹配提取
for i in title_info.split('\u3000\u3000'):
if '发布单位' in i:
fabudanwei = i.split(':')[1]
elif '所属省份' in i:
suoshushengfen = i.split(':')[1]
elif '发布时间' in i:
fabushijian = i.split(':')[1]
elif '浏览量' in i:
liulanliang = i.split(':')[1]
zhengwen_html = bs_all.find_element_by_class_name('fd-alt-all').get_attribute('outerHTML')
zhengwen = bs_all.find_element_by_class_name('fd-alt-all').text
# 构造并返回字典
dic = { '类型':detail_type,'标题':title_name,'列表标题':title,'信息':title_info,\
'发布单位':fabudanwei,'所属省份':suoshushengfen,'发布时间':fabushijian,\
'浏览量':liulanliang,'正文html':zhengwen_html,'正文':zhengwen,'url':url}
return dic
except Exception as e:
print(url,e)
return 0
2.4自主选择方法一或方法二
leixing = 2
if leixing == 2:
driver = webdriver.Chrome()
for index,detail_type,detail_id,title,url in detail.itertuples():
print('正在获取第{}条数据...,连接:{}'.format(index+1,url))
while True:
if leixing == 1:
dic = get_detail_spyder(detail_type,detail_id,title)
elif leixing == 2:
dic = get_detail(driver,detail_type,detail_id,title)
if dic:
result_list.append(dic)
break
result_df = pd.DataFrame(result_list)
四、数据分析
见下篇