动态网页抓取(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]

  当然,上面代码中导航列表的每个元素也可以传入爬虫抓取到的字段值(变量),非常灵活!

  在爬取的过程中,我们总会遇到这样的问题。总结总结前人积累的经验尤为重要。避免重复坑!

0 个评论

要回复文章请先登录注册


官方客服QQ群

微信人工客服

QQ人工客服


线