专栏首页小詹同学实战|Python轻松实现动态网页爬虫(附详细源码)

实战|Python轻松实现动态网页爬虫(附详细源码)

用浅显易懂的语言分享爬虫、数据分析及可视化等干货,希望人人都能学到新知识。

项目背景

事情是这样的,前几天我公众号写了篇爬虫入门的实战文章,叫做《实战|手把手教你用Python爬虫(附详细源码)》。发出去不到一天,一名从业10年的王律师找到了我,我虽然同意了他的微信申请,但内心是按奈不住的慌张。

简单交流了下,原来他在自学爬虫,但他发现翻页的时候,url一直不变。其实他爬取的是较高难度的网页,也就是这次要详细介绍的动态网页。一向乐于助人的J哥自然会给他指明方向,以最短的时间从青铜走向白银。

AJAX动态加载网页

什么是动态网页

J哥一向注重理论与实践相结合,知其然也要知其所以然,才能以不变应万变。

所谓的动态网页,是指跟静态网页相对的一种网页编程技术。静态网页,随着html代码的生成,页面的内容和显示效果就基本上不会发生变化了——除非你修改页面代码。而动态网页则不然,页面代码虽然没有变,但是显示的内容却是可以随着时间、环境或者数据库操作的结果而发生改变的。——来源百度百科

动态网页具有减少工作量、内容更新快、可完成功能多等特点,被很多公司所采用,比如狗东、某宝、某瓣、某乎等等。

什么是AJAX

随着人们对动态网页加载速度的要求越来越高,AJAX技术应运而生并成为许多站点的首选。AJAX是一种用于创建快速动态网页的技术,通过在后台与服务器进行少量数据交换,使网页实现异步更新。这意味着在不重新加载整个网页的情况下,可以对网页的某部分进行更新。

如何爬取AJAX动态加载网页

1. 解析接口

只要是有数据发送过来,那肯定是有发送到服务器的请求的吧。我们只需找出它悄悄加载出的页面的真实请求即可。特点:爬取速度快,爬取的数据干净,有些网站解析难度较大。

2. Selenium

selenium是什么呢?它本来是个自动化测试工具,但是被广泛的用户拿去爬虫了。它是一个工具,这个工具可以用代码操作浏览器,比如控制浏览器的下滑、模拟鼠标点击等。特点:代码较简单,爬取速度慢,容易被封ip。

项目实操

怎么说了那么多理论,说实话J哥也不想那么啰嗦。可是吧,这些东西经常会被问到,干脆直接写下来,下次还有人问就直接把这篇文章发给他,一劳永逸!

OK,咱们继续回到王律师的part。王律师作为一名资深律师,深知研究法院历年公示的开庭信息、执行信息等对于提升业务能力具有重要作用。于是,他兴高采烈地打开了一个法院信息公示网页。

长这样:

然后,他根据J哥之前写的爬虫入门文章去爬数据,成功提取了第一页,内心无比之激动。

紧接着,他加了个for循环,想着花个几分钟时间把此网站2164页共计32457条开庭公告数据提取到excel里。

然后,也就没有然后了。各位看了前面的理论部分应该也知道了,他这是 AJAX动态加载的网页。无论你怎么点击下一页,url是不会变化的。你不信我点给你看看,左上角的url像山一样矗立在那:

解析接口

既然如此,那我们就开启爬虫的正确姿势吧,先用解析接口的方法来写爬虫。

首先,找到真实请求。右键检查,点击Network,选中XHR,刷新网页,选择Name列表中的jsp文件。没错,就这么简单,真实请求就藏在里面。

我们再仔细看看这个jsp,这简直是个宝啊。有真实请求url,有请求方法post,有Headers,还有Form Data,而From Data表示给url传递的参数,通过改变参数,咱们就可以获得数据!为了安全,我把自个Cookie打了个马赛克,机智的朋友也许发现了我顺带给自个打了个广告。

我们再仔细看看这些参数,pagesnum参数不就是代表页数嘛!王律师顿悟,原来他心心念念的翻页在这里!跨过千山万水终于找到你!我们尝试点击翻页,发现只有pagesnum参数会变化。

既然发现了它,那就赶紧抓住它。J哥以迅雷不及掩耳势打开PyCharm,导入了爬虫所需的库。

from urllib.parse import urlencode
import csv
import random
import requests
import traceback
from time import sleep
from lxml import etree    #lxml为第三方网页解析库,强大且速度快

构造真实请求,添加Headers。这里J哥没有贴自己的User-Agent和Cookie,主要是一向胆小甚微的J哥害怕啊。

base_url = 'http://www.hshfy.sh.cn/shfy/gweb2017/ktgg_search_content.jsp?'  #这里要换成对应Ajax请求中的链接

headers = {
    'Connection': 'keep-alive',
    'Accept': '*/*',
    'X-Requested-With': 'XMLHttpRequest',
    'User-Agent': '你的User-Agent',
    'Origin': 'http://www.hshfy.sh.cn',
    'Referer': 'http://www.hshfy.sh.cn/shfy/gweb2017/ktgg_search.jsp?zd=splc',
    'Accept-Language': 'zh-CN,zh;q=0.9',
    'Content-Type': 'application/x-www-form-urlencoded',
    'Cookie': '你的Cookie'
}

构建get_page函数,自变量为page,也就是页数。以字典类型创建表单data,用post方式去请求网页数据。这里要注意要对返回的数据解码,编码为'gbk',否则返回的数据会乱码!另外我还加了异常处理优化了下,以防意外发生。

def get_page(page):
    n = 3
    while True:
        try:
            sleep(random.uniform(1, 2))  # 随机出现1-2之间的数,包含小数
            data = {
                'yzm': 'yxAH',
                'ft':'',
                'ktrqks': '2020-05-22',
                'ktrqjs': '2020-06-22',
                'spc':'',
                'yg':'',
                'bg':'',
                'ah':'',
                'pagesnum': page
            }
            url = base_url + urlencode(data)
            print(url)
            try:
                response = requests.request("POST",url, headers = headers)
                #print(response)
                if response.status_code == 200:
                    re = response.content.decode('gbk')
                    # print(re)
                    return re  # 解析内容
            except requests.ConnectionError as e:
                print('Error', e.args)  # 输出异常信息
        except (TimeoutError, Exception):
            n -= 1
            if n == 0:
                print('请求3次均失败,放弃此url请求,检查请求条件')
                return
            else:
                print('请求失败,重新请求')
                continue

构建parse_page函数,对返回的网页数据进行解析,用Xpath提取所有字段内容,保存为csv格式。有人会问为啥J哥这么喜欢用Xpath,因为简单好用啊!!!这么简单的网页结构搞个正则大法装x,J哥我做不到啊。

def parse_page(html):
    try:
        parse = etree.HTML(html)  # 解析网页
        items = parse.xpath('//*[@id="report"]/tbody/tr')
        for item in items[1:]:
            item = {
                'a': ''.join(item.xpath('./td[1]/font/text()')).strip(),
                'b': ''.join(item.xpath('./td[2]/font/text()')).strip(),
                'c': ''.join(item.xpath('./td[3]/text()')).strip(),
                'd': ''.join(item.xpath('./td[4]/text()')).strip(),
                'e': ''.join(item.xpath('./td[5]/text()')).strip(),
                'f': ''.join(item.xpath('./td[6]/div/text()')).strip(),
                'g': ''.join(item.xpath('./td[7]/div/text()')).strip(),
                'h': ''.join(item.xpath('./td[8]/text()')).strip(),
                'i': ''.join(item.xpath('./td[9]/text()')).strip()
            }
            #print(item)
            try:
                with open('./law.csv', 'a', encoding='utf_8_sig', newline='') as fp:
                    # 'a'为追加模式(添加)
                    # utf_8_sig格式导出csv不乱码
                    fieldnames = ['a', 'b', 'c', 'd', 'e','f','g','h','i']
                    writer = csv.DictWriter(fp,fieldnames)
                    writer.writerow(item)
            except Exception:
                print(traceback.print_exc())  #代替print e 来输出详细的异常信息
    except Exception:
        print(traceback.print_exc())

最后,遍历一下页数,调用一下函数。OK,搞定!

    for page in range(1,5):  #这里设置想要爬取的页数
        html = get_page(page)
        #print(html)
        print("第" + str(page) + "页提取完成")

我们来看一下最终效果:

Selenium

好学的朋友可能还想看看Selenium是如何来爬AJAX动态加载网页的,J哥自然会满足你的好奇心。于是赶紧新建了一个py文件,准备趁势追击,用Selenium把这网站爬下来。

首先,把相关库导进来。

from lxml import etree
import time
from selenium import webdriver
from selenium. webdriver.support.wait import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.common.by import By

然后,用chromedriver驱动打开这个网站。

def main():
    # 爬取首页url
    url = "http://www.hshfy.sh.cn/shfy/gweb2017/flws_list.jsp?ajlb=aYWpsYj3D8crCz"
    # 定义谷歌webdriver
    driver = webdriver.Chrome('./chromedriver')
    driver.maximize_window()  # 将浏览器最大化
    driver.get(url)

于是,惊喜的发现,报错了。J哥凭借着六级英语的词汇储量,居然看懂了!大概意思是我的驱动和浏览器的版本不匹配,只支持79版本的浏览器。

J哥很郁闷,因为我以前爬虫用Selenium没出现过这种问题啊。J哥不甘心,于是打开了谷歌浏览器看了下版本号。

我丢!都更新到81版本啦!既然这样,那就请好奇的小伙伴等J哥设置好浏览器自动更新、重新下载最新驱动后,下次再来听窝讲Selenium爬虫吧,记得关注此公众号,精彩不错过哦~

结 语

总结一下,对于AJAX动态加载网页爬虫,一般就两种方式:解析接口;Selenium。J哥推荐解析接口的方式,如果解析的是json数据,就更好爬了。实在没辙了再用Selenium吧。

参考链接:

Ajax:https://www.w3school.com.cn/php/php_ajax_intro.asp;

Ajax_json:https://www.jianshu.com/p/1897a8068dfb;

Examples:https://www.zhihu.com/question/46528604?sort=created

本文分享自微信公众号 - 小詹学Python(xiaozhan_Python)

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2020-10-01

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 深入理解 Python 中的上下文管理器

    操作文本对象的时候,几乎所有的人都会让我们要用 with open ,这就是一个上下文管理的例子。你一定已经相当熟悉了,我就不再废话了。

    小小詹同学
  • Python打牢基础,从12个语法开始!

    Python简单易学,但又博大精深。许多人号称精通Python,却不会写Pythonic的代码,对很多常用包的使用也并不熟悉。学海无涯,我们先来了解一些Pyth...

    小小詹同学
  • 快速入门numpy

    Numpy(Numeric Python)是一个用python实现的科学计算的扩展程序库。包括:1、一个强大的N维数组对象Array;2、比较成熟的(广播)函数...

    小小詹同学
  • 67-生成器基础

    生成器也是函数,只是常规函数通过return返回一个值,而生成器可以通过yield返回很多中间结果。

    凯茜的老爸
  • OpenCV的resize方法与双线性插值

    训练Object Detection模型SSD完毕之后进入test阶段,每张图像在进入输入层之前需要进行resize操作,以满足CNN模型对输入层size的要求...

    用户1332428
  • Github Page部署你的网页

    在我们日常开发过程中,写好的前端界面只能在本机上跑,或者同一个局域网内访问,这样很不方便。这时使用Github Page就能满足你快速轻便的部署你的前端网页。任...

    用户6094182
  • Python入库操作分这十步讲解!

    共有四个字段:id,sort,name,price,类型分别为:int,int,text,real. 其中id为primary key. 主键的取值必须是唯一的...

    double
  • 用Python打印九九乘法表—for,while循环和递归方式

    对于九九乘法表,相信大家并不陌生,但是如何将九九乘法表利用Python在控制台用不同形式输出,估计大多数刚接触Python的小朋友是不会滴。对于想熟练掌握for...

    zeruns
  • Docker系列教程28-实战:使用Docker Compose运行ELK

    https://docs.docker.com/compose/samples-for-compose/#samples-tailored-to-demo-co...

    用户1516716
  • 线性表——链式描述(双向链表)

    青木

扫码关注云+社区

领取腾讯云代金券