zoukankan      html  css  js  c++  java
  • 基于request的爬虫练习

    引言

    概述

    概念:基于网络请求的模块

    作用:用来模拟浏览器发请求,从而实现爬虫

    通用爬虫

    步骤:

    1. 指定url
    2. 请求发送:get返回的是一个响应对象
    3. 获取响应数据: text返回的是字符串形式的响应数据
    4. 持久化存储

    爬取搜狗首页的页面源码数据

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    import requests
    # 1.指定url
    url = 'https://www.sogou.com/'
    # 2.请求发送:get返回的是一个响应对象
    response = requests.get(url=url)
    # 3. 获取响应数据: text返回的是字符串形式的响应数据
    page_text = response.text
    page_text
    # 4. 持久化存储
    with open('./sogou.html','w',encoding='utf-8') as fp:
    fp.write(page_text)

    实现一个简易的网页采集器

    • 请求参数动态化(自定义字典给get方法的params传参)
    • 使用UA伪装
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    url = 'https://www.sogou.com/web'
    # 请求参数的动态化
    wd = input('enter a key word:')
    params = {
    'query':wd
    }
    # 没有做UA伪装是得不到数据的
    headers = {
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.132 Safari/537.36'
    }

    response = requests.get(url=url, params=params,headers=headers)

    # 将响应数据的编码格式手动进行指定,来解决乱码问题。
    response.encoding = 'utf-8'
    page_text = response.text
    fileName = wd + '.html'
    with open(fileName,'w',encoding='utf-8') as fp:
    fp.write(page_text)
    print(fileName,'爬取成功!')

    动态加载的数据

    • 页面中想要爬取的内容并不是请求当前url得到的,而是通过另一个网络请求请求到的数据(例如,滚轮滑到底部,会发送ajax,对局部进行刷新)

    例子:爬取豆瓣电影中动态加载出的电影详情

    我们想爬取的页面是:豆瓣电影分类排行榜 - 科幻片 https://movie.douban.com/typerank?type_name=%E7%A7%91%E5%B9%BB&type=17&interval_id=100:90&action=

    • 首先在chorme的抓包工具中进行全局搜索发现它不是请求当前url得到的,而是一个新的url: https://movie.douban.com/j/chart/top_list?

    • 当然这是一个ajax请求(在chorme的抓包工具中选择 XHR 可以进行查看)

    • 在寻找另一部电影,请求的url仍然是 https://movie.douban.com/j/chart/top_list?

    • 然后我们就需要找参数的规律了:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      type: 17
      interval_id: 100:90
      action:
      start: 0
      limit: 1
      # 第二部
      type: 17
      interval_id: 100:90
      action:
      start: 20
      limit: 20
    • start 和 limit 是可以变化的,基于例二,我们自定义字典进行传参,然后进行尝试。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    url = 'https://movie.douban.com/j/chart/top_list'
    # 参数动态化
    params = {
    'type': '5',
    'interval_id': '100:90',
    'action': '',
    'start': '01',
    'limit': '20',
    }
    # start 决定起始位置,limit 为显示数量。
    response = requests.get(url=url, params=params, headers=headers)

    # json() 返回序列化好的对象(字典,列表)
    movie_list = response.json()
    # 手动反序列化
    # import json
    # movie_list = json.loads(movie_list)
    for movie in movie_list:
    print(movie['title'],movie['score'])

    note:response.json() 可以免除我们手动使用json进行loads的过程。

    爬取肯德基的餐厅位置信息

    • 地址为:http://www.kfc.com.cn/kfccda/storelist/index.aspx

    • post请求

    • 一次爬取多页数据

    思路:

    • 首先,数据是动态加载的,分析请求方式为 post 发出的 url 与 data。
    • 通过pageSize就可以在循环内完成多所有地址的爬取。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    url = 'http://www.kfc.com.cn/kfccda/ashx/GetStoreList.ashx?op=keyword'
    data = {
    'cname':'',
    'keyword':'北京',
    'pageIndex':1,
    'pageSize':10,
    'pid':''
    }

    with open(data['keyword']+'肯德基地址.txt','w',encoding='utf-8') as f:
    for i in range(1,data['pageSize']+1):
    data['pageIndex'] = i
    address_dic = requests.post(url=url, data=data, headers=headers).json()
    for address in address_dic['Table1']:
    print(address['addressDetail'])
    f.write(address['addressDetail'] + ' ')

    补充:可以使用代理服务器,如搜索:全网代理IP

    中标公告提取

    提取内容:

    工程建设中的中标结果信息/中标候选人信息

    1. 完整的html中标信息

    2. 第一中标候选人

    3. 中标金额

    4. 中标时间

    5. 其它参与投标的公司

    思路:

    1. 从首页打开一个公告,先尝试得到一个公告的信息。
    2. 在得到另一个公告的信息进行比较,只有ID是变化的,所以有了ID就可以批量爬取了。
    3. 回到首页,判断加载方式,确定数据的请求方式,url以及参数。

    实现过程:

    • 确认爬取的数据都是动态加载出来的
    • 在首页中捕获到ajax请求对应的数据包,从该数据包中提取出请求的url和请求参数
    • 对提取到的url进行请求发送,获取响应数据(json),(包含ID信息)
    • 从json串中提取到每一个公告对应的id值
    • 将id值和中标信息对应的url进行整合,进行请求发送捕获到每一个公告对应的中标信息数据
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    # 该部分为得到一个公告的信息,但是我们这里的ID不灵活,需要进一步改进。
    params = {
    'OPtype': 'GetGGInfoPC',
    'ID': 132458,
    'GGTYPE': 4,
    'url': 'AjaxHandler/BuilderHandler.ashx',
    }
    headers = {
    'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.132 Safari/537.36',
    'Cookie': 'ASP.NET_SessionId=j0ps3kcjisaajyenlgihwwxo; Hm_lvt_94bfa5b89a33cebfead2f88d38657023=1570523570; Hm_lpvt_94bfa5b89a33cebfead2f88d38657023=1570523621; _qddagsx_02095bad0b=115b25431c8f1a2f7f607e8464ba7c5ef5807a77e65a44aa3c9045306ab0ba3bf02c48523e97b816f16d9ff0c57b6e77f46e59f8776b88c64cbd9da7f84676d8c4c9db3686235ef49e9ee7ff1871ec99884e7ba79b8c173e472b039b0c9a8fb61b4049ab036f68e1f5e0857c4bd4131f0c3b7478b98687d0b9c0538352871ec9',
    }
    url = 'https://www.fjggfw.gov.cn/Website/AjaxHandler/BuilderHandler.ashx'
    response = requests.get(url=url, params=params, headers=headers)
    response.text

    完成了对公告详情的爬取后,接下来批量爬取公告。

    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
    # 该部分完成了对前5页的公告信息进行爬取
    url = 'https://www.fjggfw.gov.cn/Website/AjaxHandler/BuilderHandler.ashx'
    headers = {
    'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.132 Safari/537.36',
    'Cookie': 'ASP.NET_SessionId=j0ps3kcjisaajyenlgihwwxo; Hm_lvt_94bfa5b89a33cebfead2f88d38657023=1570523570; Hm_lpvt_94bfa5b89a33cebfead2f88d38657023=1570523621; _qddagsx_02095bad0b=115b25431c8f1a2f7f607e8464ba7c5ef5807a77e65a44aa3c9045306ab0ba3bf02c48523e97b816f16d9ff0c57b6e77f46e59f8776b88c64cbd9da7f84676d8c4c9db3686235ef49e9ee7ff1871ec99884e7ba79b8c173e472b039b0c9a8fb61b4049ab036f68e1f5e0857c4bd4131f0c3b7478b98687d0b9c0538352871ec9',
    }
    data = {
    'OPtype': 'GetListNew',
    'pageNo': 1,
    'pageSize': 10,
    'proArea': -1,
    'category': 'GCJS',
    'announcementType': -1,
    'ProType': -1,
    'xmlx': -1,
    'projectName': '',
    'TopTime': '2019-07-10 00:00:00',
    'EndTime': '2019-10-08 23:59:59',
    'rrr': 0.8853761868569314,
    }
    params = {
    'OPtype': 'GetGGInfoPC',
    'ID': 132458,
    'GGTYPE': 4,
    'url': 'AjaxHandler/BuilderHandler.ashx',
    }
    count = 0
    for i in range(1,6):
    data['pageNo'] = i
    # 第i页内容的爬取
    response = requests.post(url=url,data=data,headers=headers)
    post_data = response.json()
    for row in post_data['data']:
    ID = int(row['M_ID'])
    params['ID'] = ID
    company_respose = requests.get(url=url, params=params, headers=headers)
    company_detail = company_respose.json()['data']
    # print(company_detail)
    count += 1
    print(count)

    爬取图片

    如何做?

    • 基于requests
    • 基于urllib
    • 区别:urllib中的 urlretrieve 不可以进行UA伪装
      requests在urllib基础上产生,更加pythonic!

    基于requests的图片爬取

    1
    2
    3
    4
    5
    6
    7
    8
    9
    import requests
    headers = {
    'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.132 Safari/537.36'
    }
    # 基于requests的图片爬取
    url = r'https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1570593174006&di=5ade9e1cb9e63a708c69095283f8e40a&imgtype=0&src=http%3A%2F%2Fdingyue.nosdn.127.net%2F92Ot1vmaeOklEbu2G7ABakMeGiYWpYi8R3urPnggBDJSs1535663928397.jpeg'
    img_data = requests.get(url=url, headers=headers).content # content 返回的是bytes类型的响应数据
    with open('./123.jpg','wb') as fp:
    fp.write(img_data)
    • request更具通用性,数据可以展示为:text(字符串),json(列表/字典),content(字节)。

    基于urllib的图片爬取

    1
    2
    3
    4
    # 基于urllib
    from urllib import request
    url = 'https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1570593174006&di=5ade9e1cb9e63a708c69095283f8e40a&imgtype=0&src=http%3A%2F%2Fdingyue.nosdn.127.net%2F92Ot1vmaeOklEbu2G7ABakMeGiYWpYi8R3urPnggBDJSs1535663928397.jpeg'
    request.urlretrieve(url,'./456.jpg')
    • 由于urlretrieve不能做UA伪装,所以存在图片缺失的可能。

    聚焦爬虫

    较通用爬虫相比,增加了数据解析

    数据解析

    概念:将一整张页面的局部数据进行提取/解析

    作用:用来实现聚焦爬虫

    实现方式:

    • 正则
    • bs4
    • xpath
    • pyquery

    数据解析的通用原理是什么?

    • 标签的定位
    • 数据的提取

    页面中的相关的字符串的数据都存储在哪里?

    • 标签中间
    • 标签的属性中

    基于聚焦爬虫的编码流程

    • 指定url
    • 发起请求
    • 获取响应数据
    • 数据解析
    • 持久化存储

    正则解析

    爬取煎蛋网中的图片

    • 地址为:http://jandan.net/pic/MjAxOTEwMDktNjk=#comments

    实现过程:

    • 指定url
    • 获取响应数据
    • 数据解析
      • 写正则表达式
      • 正则匹配
    • 持久化存储
    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
    import re
    import os
    url = 'http://jandan.net/pic/MjAxOTEwMDktNjk=#comments'
    page_text = requests.get(url=url,headers=headers).text
    # 解析数据:img标签的src的属性值

    # 写正则表达式
    # print(page_text) # 参考page_text来定正则,网页源码可能有些小问题
    # ex = r'<div class="text">.*?<img src="(.*?)" referrerpolicy' # 坑!!!
    ex = r'<div class="text">.*?<img src="(.*?)" referrerPolicy' # 在源码中那里的p是小写,可爬下来的p是大写,坑~~~

    dirName = './JDimg/'
    if not os.path.exists(dirName):
    os.mkdir(dirName)
    img_url_list = re.findall(ex,page_text,re.S) # re.S处理回车
    for img_url in img_url_list:
    ex2 = r'org_src="(.*?)"'
    gif_url = re.findall(ex2,img_url)
    if gif_url:
    new_url = 'http:' + gif_url[0]
    else:
    new_url = 'http:' + img_url
    name = new_url.split('/')[-1]
    img = requests.get(new_url,headers=headers).content
    img_path = dirName + name
    with open(img_path,'wb') as fp:
    fp.write(img)
    print(name,'下载完成!!!')

    note:内容依据响应数据page_text,而不是根据浏览器中网页上的代码,上面就出现一个字母的大小写不同而导致正则失效的例子。

    bs4解析

    环境的安装:

    • pip install bs4
    • pip install lxml

    bs4的解析原理:

    • 实例化一个BeatifulSoup的一个对象,把即将被解析的页面源码数据加载到该对象中
    • 需要调用BeatifulSoup对象中的相关的方法属性进行标签定位和数据的提取

    BeatifulSoup的实例化

    • BeatifulSoup(fp, ‘lxml’) 将本地存储的html文档中的页面源码数据加载到该对象中
    • BeatifulSoup(page_text, ‘lxml’) 将从互联网中请求到的页面源码数据加载到该对象中

    标签的定位

    • soup.tagName: 只可以定位到第一个tagName标签

    • 属性定位:

      • soup.find(‘div’,’attrName=’value’) 只能定位到符合要求的第一个
      • soup.findAll:返回列表,可以定位到符合要求的所有标签
        note:只有class需要加下划线,其它直接用原名就可以。
    • 选择器定位:

      • select(‘选择器’)
      • 选择器:id,class,tag,层级选择器(大于号表示一个层级,空格表示多个层级)
    • 取文本

      • text:将标签中所有文本取出
      • string:将标签中直系的文本取出
    • 取属性

      • tag[‘attrName’]

    熟悉

    此为练手的html。

    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
    <html lang="en">
    <head>
    <meta charset="UTF-8" />
    <title>测试bs4</title>
    </head>
    <body>
    <div>
    <p>百里守约</p>
    </div>
    <div class="song">
    <p>李清照</p>
    <p>王安石</p>
    <p>苏轼</p>
    <p>柳宗元</p>
    <a href="http://www.song.com/" title="赵匡胤" target="_self">
    <span>this is span</span>
    宋朝是最强大的王朝,不是军队的强大,而是经济很强大,国民都很有钱</a>
    <a href="" class="du">总为浮云能蔽日,长安不见使人愁</a>
    <img src="http://www.baidu.com/meinv.jpg" alt="" />
    </div>
    <div class="tang">
    <ul>
    <li><a href="http://www.baidu.com" title="qing">清明时节雨纷纷,路上行人欲断魂,借问酒家何处有,牧童遥指杏花村</a></li>
    <li><a href="http://www.163.com" title="qin">秦时明月汉时关,万里长征人未还,但使龙城飞将在,不教胡马度阴山</a></li>
    <li><a href="http://www.126.com" alt="qi">岐王宅里寻常见,崔九堂前几度闻,正是江南好风景,落花时节又逢君</a></li>
    <li><a href="http://www.sina.com" class="du">杜甫</a></li>
    <li><a href="http://www.dudu.com" class="du">杜牧</a></li>
    <li><b>杜小月</b></li>
    <li><i>度蜜月</i></li>
    <li><a href="http://www.haha.com" id="feng">凤凰台上凤凰游,凤去台空江自流,吴宫花草埋幽径,晋代衣冠成古丘</a></li>
    </ul>
    </div>
    </body>
    </html>

    熟悉 BeautifulSoup 的选择器以及如何取文本和属性

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    fp = open('./test.html',encoding='utf-8') 
    soup = BeautifulSoup(fp ,'lxml')
    soup.div
    type(soup.div) # bs4.element.Tag
    soup.find('div')
    soup.find('div',class_="song") # 由于class 是关键字,所以需要加下划线

    #下面的 findAll 与 select 都将返回列表
    soup.findAll('div')
    soup.select('div')
    soup.select('div[class="song"]')
    soup.select('.song')
    soup.select('div > ul > li')
    soup.select('div li')

    # 取文本
    soup.select('div li')[0].text
    soup.select('.song')[0].text
    soup.select('.song')[0].string # 只能取直系,所以这个结果为空
    soup.select('b')[0].string

    # 取属性
    soup.select('div a')[0]['href']

    爬取三国演义小说的标题和内容

    • 地址为:http://www.shicimingju.com/book/sanguoyanyi.html

    数据解析流程:

    • 首先定位标签
    • 然后取出文本和内容
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    main_url = 'http://www.shicimingju.com/book/sanguoyanyi.html'
    page_text = requests.get(url=main_url,headers=headers).text
    # 数据解析: 章节的标题和详情页的url
    soup = BeautifulSoup(page_text,'lxml')
    li = soup.select('.book-mulu a')
    fp = open('./sanguo.txt','w',encoding='utf-8')
    for i in li:
    title = i.string
    detail_url = 'http://www.shicimingju.com' + i['href']
    detail_page_text = requests.get(url=detail_url,headers=headers).text
    # 数据解析:章节内容
    detail_soup = BeautifulSoup(detail_page_text,'lxml')
    detail = detail_soup.find('div',class_='chapter_content').text
    fp.write(title + ' ' + detail + ' ')
    fp.close()

    xpath 解析

    • 环境的安装

      • pip install lxml
    • 解析原理

      • 实例化一个etree的对象,且把即将被解析的页面源码数据加载到该对象中
      • 调用etree对象中的xpath方法结合着不同形式的xpath表达式进行标签定位和数据提取
    • etree对象的实例化

      • etree.parse(‘fileName’)
      • etree.HTML(page_text)
    • 标签定位

      • 最左侧的/:一定要从根标签开始进行标签定位
      • 非最左侧的/:表示一个层级
      • 最左侧的//:可以从任意位置进行指定标签的定位
      • 非最左侧的//:表示多个层级
      • 属性定位:
        • //tagName[@attrName=”value”]
      • 索引定位:
        • //tagName[@attrName=”value”]/li[2],索引是从1开始的
      • 逻辑运算:
        - 找到href属性值为空且class属性值为du的a标签
        • //a[@href=”” and @class=”du”]
      • 模糊匹配:
        • //div[contains(@class, “ng”)]
        • //div[starts-with(@class, “ta”)]
    • 取文本

      • /text():取直系的文本内容
      • //text():取所有文本内容
    • 取属性

      • /@attrName

    note:

    • 返回的都是列表
    • 在google中的Element可以直接copy xpath

    熟悉

    依旧使用上面的html练手

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    from lxml import etree
    tree = etree.parse('./test.html')
    tree # <lxml.etree._ElementTree at 0x231b25371c8>
    tree.xpath('/html')
    tree.xpath('/html//title')
    tree.xpath('//div')
    # 属性定位
    tree.xpath('//*[@class="tang"]')
    tree.xpath('//div[@class="song"]')
    tree.xpath('//div[@class="tang"]/ul/li')
    # 索引定位
    tree.xpath('//div[@class="tang"]/ul/li[2]')

    # 取文本
    tree.xpath('//b/text()') # 取直系
    tree.xpath('//div[@class="tang"]/ul/li//text()') # 取所有

    # 取属性
    tree.xpath('//a/@href')

    爬取虎牙主播名称,热度和标题

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    url = 'https://www.huya.com/g/xingxiu'
    page_text = requests.get(url,headers=headers).text
    tree = etree.HTML(page_text)
    li_list = tree.xpath('//div[@class="box-bd"]/ul/li')
    # print(page_text)
    for li in li_list:
    title = li.xpath('./a[2]/@title')[0]
    name = li.xpath('./span/span[1]/i/@title')[0]
    hot = li.xpath('./span/span[2]/i[2]/text()')[0]
    print(name,'的直播间: ',title,'热度为:',hot)

    爬取所有页码的妹子

    地址为:http://pic.netbian.com/4kmeinv/

    • 涉及中文乱码
      • 以前对response的 encoding 这样代价大
        iso-8859-1 –> utf-8 先编码成iso-8859-1 在解码成 utf或gbk
    • 多页码数据的爬取
      • 制定一个通用的url模板
  • 相关阅读:
    第一阶段用户模板和用户场景
    团队开发冲刺第十二天
    第十周总结
    团队开发冲刺第十一天
    团队开发冲刺第十天(实现页面展示评论数与点赞数)
    团队开发冲刺第九天(实现收藏,点赞,关注功能)
    团队开发冲刺第八天(实现评论功能)
    软件用户场景分析
    第九周总结
    团队开发冲刺第六天(新闻详情页的展示)
  • 原文地址:https://www.cnblogs.com/taosiyu/p/11645901.html
Copyright © 2011-2022 走看看