动态网页抓取(Requests模拟浏览器发送Post请求时,发现程序返回的html与浏览器F12)
优采云 发布时间: 2021-12-16 07:28动态网页抓取(Requests模拟浏览器发送Post请求时,发现程序返回的html与浏览器F12)
最近在用Requests模拟浏览器发送Post请求的时候,发现程序返回的html和浏览器F12观察到的有点不一样。经过观察,返回了response.text,确认cookies是有效的,因为我们可以看到login返回了。信息。但是,某些字段仍显示空值。
下图是浏览器F12抓包看到的界面:
由于笔者观察到浏览器捕捉到的Response(html文件)与查看第一个界面请求时页面显示的信息是一致的,所以简单的认为只需要使用requests库来构造这个请求即可。但是,其实第一种形式只返回首页的frame,很多数据通过script、XHR等格式请求返回数据后动态加载到基本frame页面中。
然后随便挑关键点,请求下面的key list.do等xhr信息?
在这种情况下,这是不可能的。整个前端网页的内容填充分为模块。每个js文件或者后端返回的json只确定了部分页面信息,这就导致需要模拟多个页面才能完整获取页面信息。问。更重要的是,前端页面的部分信息是结合后台返回的json文件,经过一定的正则计算后返回的最终结果。如果在页面中找不到值的后端算术函数,我们就无法模拟后端服务器的行为来构造相同的函数。
这种由多个JavaScript文件渲染后生成的网页,直接用requests库爬取比较困难。
这时候通过查阅资料,发现解决javascript动态生成页面信息爬取的方法有两种:(参考博客:)
1.1 使用dryscrape库动态抓取页面
Node.js 脚本通过浏览器执行并返回信息。所以js执行后抓取页面最直接的方法之一就是用python模拟浏览器的行为。WebKit 是一个开源浏览器引擎。Python 提供了许多库来调用这个引擎。干刮就是其中之一。它调用webkit引擎来处理收录js等的网页!ps:由于其底层操作逻辑(python调用webkit请求页面,页面加载后加载js文件,让js执行,返回执行的页面),实际过程比较慢。
import dryscrape
# 使用dryscrape库 动态抓取页面
def get_url_dynamic(url):
session_req=dryscrape.Session()
session_req.visit(url) #请求页面
response=session_req.body() #网页的文本
#print(response)
return response
get_text_line(get_url_dynamic(url)) #将输出一条文本
1.2 使用selenium完成动态页面的爬取
Selenium 是一个网页测试框架,它允许调用本地浏览器引擎发送网页请求,因此也可以实现抓取网页的要求。
这也是作者之前大部分文章推荐的框架。所谓的“看到就爬”,可惜效率还是比后台请求的请求方式慢很多。如果可以结合Chrome浏览器的headless模式进行静默爬行,可以稍微提高效率。开启无头模式的代码示例:
from selenium import webdriver
option = webdriver.ChromeOptions()
option.add_argument(\'headless\')
driver = webdriver.Chrome(chrome_options=option)
driver.get(url) #访问网址
page_content=driver.page_source #获取js选然后的页面源码
上诉操作后,我们就可以得到页面最终的源码了。
但是在实际使用中,selenium 还是有一个问题,就是“可见就可以爬取”。在源码中可以清楚看到的一些页面元素。如果页面显示在前台,需要点击才能出现,我们还要模拟浏览器行为,使用click()方法点击获取相关节点的数据。喜欢:
如果页面停留在“基本信息”界面,如果要获取“审批信息”标签页的信息,则需要模拟点击“审批信息”,这会在一定程度上降低抓取效率。
此时建议直接使用 BeautifulSoup 包解析 html 文件,然后直接用通用正则表达式 RE 获取,尽量不要模拟浏览器不急的点击行为(除非页面源码有不存在,需要点击触发js动态返回信息条件)。
下面是结合源码和bs4(BeautifulSoup)的例子,在我的实际工作中重新表达爬取特定字段:
whole_text=driver.page_source #提取加载后的源码
soup=BeautifulSoup(whole_text,"lxml")
haf=str(soup.select(\'script\')[6]) #得到haf字段,再进行后续提取
flowHiComments=re.search(\'.*?flowHiComments\":(.*?),\"flowHiNodeIds.*?\',haf,re.S)
applyerId=soup.find(id="afPersonId")[\'value\'] #根据id查找
applyerName=re.search(\'.*?applyerName\":\"(.*?)\".*?\',haf,re.S).group(1) #根据re表达式的group方法提取字符串特定字段
flowHiComments=json.loads(flowHiComments.group(1)) #得到页面评论信息
with open(\'.\\CommentFlow\\%s_%s.txt\'%(bpmDefName,afFormNumber),\'w\',encoding="utf-8") as txt: #将flowHiComments保存为本地txt文件,并对文件进行格式化
json.dump(flowHiComments,txt,ensure_ascii = False,indent=4)
当你得到一个特定的字段时,你需要将信息一一存储。例如,将信息保存在本地 excel 文件中。这时候就需要用到openpyxl文件了。
openpyxl文件对excel新格式比较友好。在实际使用中还有一些需要注意的地方:
1.openpyxl 提供默认返回的excel最后一行(列)的索引号:
可以使用 ws.mas_row 和 ws.max_column 两个原生方法,但是如果我们想读取任何列的最后一行号怎么办?ws.max_row 不是那么灵活。
如果想把A列的所有元素都放到内存中,可以使用的示例代码如下:
name=[]
while True:
if sheet.cell(num,1).value ==None:
break
name.append(sheet.cell(num,1).value) #名称
num+=1
同理可以得到B列的值。
2. 我们习惯于使用 ws.append() 方法将数据按行追加到表中。根据实际测量,每次添加内容都是从第二行开始的(是否考虑第一行作为标题行),如果我们想让程序在执行过程中动态添加一个标题行呢?
笔者实测:
from openpyxl import load_workbook
wb= load_workbook(\'test2.xlsx\')
#wb.active =1
sheet=wb["Sheet1"]
row = [1 ,2, 3, 4, 5]
sheet.append(row)
wb.save(\'test2.xlsx\')
结果执行后,excel端生成的数据从第二行开始:
这显然有时不能满足我们的要求。如果要传递第一行的值,不建议使用原生的append方法。可行的建议如下:
navigation=[]
if ws.cell(1,1).value ==None:
navigation=["名称","单号","业务描述","申请者","申请者编号","代码","备注"]
for m in range(len(navigation)):
ws.cell(1,m+1).value=navigation[m]
当然,上面代码中导航列表的每个元素也可以传入爬虫抓取到的字段值(变量),非常灵活!
在爬取的过程中,我们总会遇到这样的问题。总结总结前人积累的经验尤为重要。避免重复坑!