• Python 爬虫小练习:基于 XPath 的表格信息爬取


    这是一个 Python 爬虫的入门练习,我们通过 Request 请求数据,并通过 XPath 去匹配表格中的元素或者内容,使用 Pandas 进行数据的整理。下面我们 Step by Step 来完成这件事情。

    确定目标和分析思路

    目标

    目标是从某个网站中爬取表格数据。我选择的网站是:http://py.ucas.ac.cn/zh-cn/zhuanjia/ZhuanjiaList (下不妨称为名单页)。

    • 这里有一个名单库,名单库里面有一些个人信息,包括姓名、单位名称等等。
    • 名单分页,需要不断点下一页加载更多信息。
    • 更多的信息需要点击名单上的姓名进入子页(下称为详情页)进行查看。需要循环点击,二级爬取。

    思路

    思路是我们通过名单页上的 XPath 选择和翻页,获得所有对应着每个人的详情页链接。之后遍历详情页链接,爬取信息,存为 csv 文件。

    观察情况

    按 F12 可以打开浏览器的开发者工具,选中 Network 页。

    刷新名单页,可以看到信息流,包括 Request URL 和 Request Headers 等等。因为是密码登录的,我们在发送请求时需要 Cookie 作为头信息。

    点开第二页名单页,我们发现翻页只需要修改 url 链接 page= 后面的内容:http://py.ucas.ac.cn/zh-cn/zhuanjia/ZhuanjiaList?pinqing_dwdm=80002&is_yw=False&page=xxx

    清空 Network 信息流,随意点一个名字,打开改名字对应的详情页。观察到,它的 Request URL 是:http://py.ucas.ac.cn/zh-cn/zhuanjia/ZhuanjiaEdit?zhuanjia_id=xxxxx。它以 get 的方式请求信息,且 zhuanjia_id 对应着每个人的 ID,只要改变这个 ID,就会返回不同人的信息。容易看到,ID 的信息,可以在名单页的姓名栏的 href 中找到。

    尝试了,通过 pandas 的 pandas.read_html 读取网上的表格和基本的数据格式化操作在此处比较麻烦,故而,直接使用 XPath 定位。

    爬取名单表

    按照上述的思路,我们分两步走,先使用名单页,通过翻页汇总所有人对应的详情页信息。可以观察到的是

    写好 Headers,通过 requests.get 返回网页信息,再利用 Xpath 进行提取之后存档。

    代码如下:

    import requests
    from lxml import etree
    import pandas as pd
    # parameter = {
    #             "key1":"value1",
    #             "key2":"value2"
    #             }
    headers = {"Cache-Control": "max-age=0",
               "Connection":"keep-alive",
               "Cookie":"xxxxxxxxxx",
               "Referer":"http://py.ucas.ac.cn/zh-cn/zhuanjia/ZhuanjiaList?pinqing_dwdm=80002&is_yw=False&page=2",
               "Upgrade-Insecure-Requests":"1",
               "User-Agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/107.0.0.0 Safari/537.36"
        }
    total_page = 2111
    dfs = pd.DataFrame(columns=['name','id']);
    ind = 0
    for i in range(2111,total_page+1):
        response = requests.get("http://py.ucas.ac.cn/zh-cn/zhuanjia/ZhuanjiaList? \
                                pinqing_dwdm=80002&is_yw=False&page="+str(i), \
                                headers = headers)#,params = parameter)
        # print(response.url)
        # print(response.text)
        # print(response.content)
        # print(response.encoding)
        # print(response.status_code)
        html=response.text
        tree=etree.HTML(html)
        NAME = tree.xpath('//*[@id="main-content"]/div/div/div/div/div[2]/div/table/tbody/tr/td[1]/a')
        IDHREF = tree.xpath('//*[@id="main-content"]/div/div/div/div/div[2]/div/table/tbody/tr/td[1]/a/@href')
        for j in range(0,len(NAME)):
            ind = ind+1
            print(ind)
            ids = str(IDHREF[j]).split('=')
            new = pd.DataFrame({'name':NAME[j].text, 'id':int(ids[1])},index=[ind])
            dfs=dfs.append(new) 
    dfs.to_csv("./教授名字和ID.csv", encoding="utf_8_sig")
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37

    这里没有特别需要强调的地方,只不过这份代码不是写得非常高性能,有很多可以优化的地方,比如只要一个 tree.xpath。

    爬取详情页二级信息

    假设已经得到了爬取详情页需要 ID,这时候,我们可以通过不断地对微小区别的 url 们发送请求,就可以得到所以详情页信息。再通过开发者工具右键 XPath 拷贝工具,得到 XPath 匹配模式,粘贴提取信息,通过 Pandas 整理,存档。所用的代码如下:

    import requests
    from lxml import etree
    import pandas as pd
    ID1 = pd.read_csv('./教授名字和ID0.csv')
    ID2 = pd.read_csv('./教授名字和ID.csv')
    ID1n = list(map(int, ID1['id'])) 
    ID2n = list(map(int, ID2['id'])) 
    IDs = ID1n+ID2n
    IDs.sort()
    
    header = {"Connection": "keep-alive",
    "Cookie": "xxxx",
    "Host": "py.ucas.ac.cn",
    "Referer": "http://py.ucas.ac.cn/zh-cn/zhuanjia/ZhuanjiaList?pinqing_dwdm=80002&is_yw=False&page=1936",
    "Upgrade-Insecure-Requests": "1",
    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/107.0.0.0 Safari/537.36"
    }
    bg = 20617;
    ed = len(IDs)
    #ed = 2
    data = pd.DataFrame(columns=['姓名','性别','单位','通讯地址','邮编','邮箱','手机',\
                                 '固话','出生日期','身份证号','学科专业','方向','导师类别',\
                                     '专业技术职务','银行户头','银行地址','银行号码'])
    count = -1
    for i in range(bg,ed):
        count = count+1
        id = IDs[i]
        print(str(IDs[bg])+"TO"+str(IDs[ed-1])+":"+str(i)+":"+str(id)+"进行中...")
        response = requests.get("http://py.ucas.ac.cn/zh-cn/zhuanjia/ZhuanjiaEdit?zhuanjia_id="+str(id),headers=header)
        html=response.text
        # print(html)
        # with open('test.html','w',encoding='utf-8') as f:
        #     f.write(html)
        tree=etree.HTML(html)
        zjxm = str(tree.xpath('//*[@id="ZJXM"]/@value')[0]) #姓名
        zjxb = str(tree.xpath('//*[@id="ZJXB"]/option[@selected="selected"]/@value')[0])#性别
        jsdw = str(tree.xpath('//*[@id="DWDM"]/option[@selected="selected"]')[0].text)#单位
        zjtxdz = str(tree.xpath('//*[@id="ZJTXDZ"]/@value')[0])#通讯地址
        zjyzbm = str(tree.xpath('//*[@id="ZJYZBM"]/@value')[0])#邮编
        UserName = str(tree.xpath('//*[@id="UserName"]/@value')[0])#邮箱
        zjsj = str(tree.xpath('//*[@id="ZJSJ"]/@value')[0])#手机
        zjtel = str(tree.xpath('//*[@id="ZJTEL"]/@value')[0])#固话
        zjcsrq = str(tree.xpath('//*[@id="ZJCSRQ"]/@value')[0])#出生日期
        zjhm = str(tree.xpath('//*[@id="zjhm"]/@value')[0])#身份证号
        tmp = tree.xpath('//*[@id="ZJ_XKZY"]/option[@selected="selected"]')
        if len(tmp)==0:
            zjxkzy = ''
        else:
            zjxkzy = str(tmp[0].text)#学科专业
        yjfx = str(tree.xpath('//*[@id="yjfx"]/@value')[0])#方向  
        zjlb = str(tree.xpath('//*[@id="ZJLB"]/option[@selected="selected"]')[0].text)#导师类别
        tmp = tree.xpath('//*[@id="ZJZWCODE"]/option[@selected="selected"]')
        if len(tmp)==0:
            zjzwcode = ''
        else:
            zjzwcode = str(tmp[0].text)#专业技术职务  
        bank_hutou = str(tree.xpath('//*[@id="bank_hutou"]/@value')[0])#银行户头
        bank_dizhi = str(tree.xpath('//*[@id="bank_dizhi"]/@value')[0])#银行地址
        bank_xingming = str(tree.xpath('//*[@id="bank_xingming"]/@value')[0])#银行号码
        data.loc[count] = [zjxm,zjxb,jsdw,zjtxdz,zjyzbm,UserName,zjsj,zjtel,zjcsrq,zjhm,zjxkzy,yjfx,zjlb,zjzwcode,bank_hutou,bank_dizhi,bank_xingming]
    data.to_csv("./"+str(bg)+"_"+str(IDs[bg])+"TO"+str(i)+"_"+str(IDs[i])+"高校教授联系方式.csv", encoding="utf_8_sig")
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61

    我们知道,长时间的爬取,总会出现一些问题,比如网络拒绝问题。在我的代码中个,没有任何的 try 方式的异常捕捉。我采用的异常处理方式时,代码跑断了,先把中间结果手动地存下来。再手动地从断点出手动地开启代码重跑。
    我享受这种手动挡的过程,而不需要任何的异常处理机制。

    爬虫请遵守相关法律法规,不要做违法犯罪的事情

    爬虫写得好,牢饭吃到饱。不爬私有数据,不把网站搞崩。不要将爬虫方法用于任何的盈利。

    爬虫小技巧总结

    1. Request 的 Headers 信息可以通过直接从浏览器开发者工具信息流里面拷贝修改得到。

    2. 匹配信息的时候建议存一份 Python 请求返回的网页进行分析,而不是在浏览器中直接访问请求地址,根据返回的结果做元素匹配。因为有时候 Python 请求的得到的结果和浏览器请求得到的结果是不一样的。存 Python 返回的网页结果的代码示例如下:

      response = requests.get("http://py.ucas.ac.cn/zh-cn/zhuanjia/ZhuanjiaEdit?zhuanjia_id="+str(id),headers=header)
      html=response.text
      print(html)
      with open('test.html','w',encoding='utf-8') as f:
      	f.write(html)
      
      • 1
      • 2
      • 3
      • 4
      • 5
    3. 可以通过鼠标右键,检查元素,Copy XPath 得到 XPath 的匹配模式,黏贴到代码中接口。但寻找的结果为空或者不对的时候,可以对你要找的元素的姊妹单元复制 XPath,进行比对分析。

    4. 擅 XPath @ 功能。当我们要去 XPath 要取标签里面的元素的值的时候,可以用 @ 进行取值,如:tree.xpath('//*[@id="DWDM"]/option[@selected="selected"]')[0].text 此中中括号本表示索引,option[@selected="selected"] 表示在所有的 option 里面选择满足条件的(这里是下拉框产生的)。再比如 tree.xpath('//*[@id="ZJXM"]/@value')[0] 中 /@value 表示在所有 //*[@id="ZJXM"] 中取 value 的值,组成 list。

    5. 整理一份 Request+XPath 的模板如下,每次想要爬东西,复制修改即可用:

      import requests
      from lxml import etree
      import pandas as pd
      # parameter = {
      #             "key1":"value1",
      #             "key2":"value2"
      #             }
      header = {"Connection": "keep-alive",
      "Cookie": "xxx",
      "Host": "py.ucas.ac.cn",
      "Referer": "http://py.ucas.ac.cn/zh-cn/zhuanjia/ZhuanjiaList?pinqing_dwdm=80002&is_yw=False&page=1936",
      "Upgrade-Insecure-Requests": "1",
      "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/107.0.0.0 Safari/537.36"
      }
      data = pd.DataFrame(columns=['姓名','单位'])
      response = requests.get("http://py.ucas.ac.cn/zh-cn/zhuanjia/ZhuanjiaEdit?zhuanjia_id=xxx",headers=header)#,params = parameter)
      html=response.text
      print(html)
      # print(response.url)
      # print(response.text)
      # print(response.content)
      # print(response.encoding)
      # print(response.status_code)
      with open('test.html','w',encoding='utf-8') as f:
          f.write(html)
      tree=etree.HTML(html)
      zjxm = str(tree.xpath('//*[@id="ZJXM"]/@value')[0]) #姓名
      jsdw = str(tree.xpath('//*[@id="DWDM"]/option[@selected="selected"]')[0].text)#单位
      data.loc[0] = [zjxm,jsdw]
      data.to_csv("xxx.csv", encoding="utf_8_sig")
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24
      • 25
      • 26
      • 27
      • 28
      • 29
      • 30
  • 相关阅读:
    WorldCoin 运营数据,业务安全分析
    Servlet容器使用Spring容器
    【老生谈算法】matlab实现图论程序算法大全——图论程序算法大全
    顺序表和ArrayList
    【配置】【IDE】使用vscode调试typescript应用
    YOLOv5算法改进(16)— 增加小目标检测层 | 四头检测机制(包括代码+添加步骤+网络结构图)
    [Python进阶] 消息框、弹窗:tkinter库
    Qt utc时间字符串转为本地时间
    Scala012--Scala中的常用集合函数及操作Ⅲ
    spring-初识spring
  • 原文地址:https://blog.csdn.net/lusongno1/article/details/128195834