【Python网络爬虫】01.爬虫原理,爬虫常用库入门练习

我的博客,欢迎阅读 https://blog.starmeow.cn

爬虫原理和网页构造

网络连接原理:

  • 计算机----Request(请求头和消息体)--->服务器
  • 计算机<---Respone(HTML文件)----服务器

爬虫原理:

  1. 模拟计算机对服务器发起Request请求;
  2. 接收服务器的Response内容并解析、提取所需的信息

设计爬虫流程

多页面和跨页面爬虫流程。

多页面爬虫流程

网页存在多页的情况,每页结构相同或相似。

  1. 手动翻页观察各URL构成特点,构造成所有页面的URL存入列表;
  2. 根据URL列表依次循环取出URL;
  3. 定义爬虫函数;
  4. 循环调用爬虫函数,存储数据;
  5. 循环完毕,结束爬虫程序。

跨页面爬虫流程

一个页面存在很多链接,每个链接进去对应一个详情页。

  1. 定义爬虫函数爬取列表页所有专题的URL;
  2. 将专题URL存入列表中(种子URL);
  3. 定义爬取详细页数据函数;
  4. 进入专题详细页面爬取详细页数据;
  5. 存储数据,循环完毕,结束爬虫程序。

网页构造

浏览器按F12,点击Elements可以看到网页的元素,包括HTMLCSSJavaScript

爬虫初步入门

创建虚拟环境

>mkvirtualenv Crawler
>workon Crawler
>pip install requests
>pip install lxml
>pip install beautifulsoup4

爬虫三大库

Requests库

http://docs.python-requests.org/zh_CN/latest/user/quickstart.html

它的作用就是请求网站获取网页数据的。

import requests

res = requests.get(url='https://blog.starmeow.cn/blog/1/detail/')
print(res)  # ,如果为40x则请求失败

print(res.text)  # 显示网页所有内容和F12结果一样

后端显示的浏览器Agent信息为:python-requests/2.20.0,意思就是通过requests库访问的

有时爬虫需要加入请求头来伪装成浏览器,以便更好地抓取数据。

image.png

请求头的使用方法

import requests

headers = {
    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.77 Safari/537.36"
}
res = requests.get(url='https://blog.starmeow.cn/blog/1/detail/', headers=headers)  # get方法加入请求头
print(res)  # ,如果为40x则请求失败

print(res.text)  # 显示网页所有内容和F12结果一样

然后再查看网页的请求Agent信息就是Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.77 Safari/537.36

Requests库不仅有get()方法,还有post()方法。post()方法用于提交表单来爬取需要登录才能获取数据的网站。

错误和异常

requests.exceptions的官方文档 http://www.python-requests.org/en/master/_modules/requests/exceptions/#RequestException

  • Requests抛出一个ConnectionError异常,这个是网络问题(如DNS查询失败,拒绝连接等)
  • Response.raise_for_status()抛出一个HTTPError异常,为HTTP请求返回了不成功的状态码(如网页不存在,返回404错误)
  • Requests抛出一个Timeout异常,原因为请求超时
  • Requests抛出一个TooManyRedirects异常,为请求超过了设置的最大重定向次数

所有Requests显示抛出的异常都继承requests.exceptions.RequestException,爬取过程如果遇到错误爬虫就会停止,可以通过try来避免异常。

import requests

headers = {
    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.77 Safari/537.36"
}

try:
    res = requests.get(url='https://blog.starmeow0.cn/blog/1/detail/', headers=headers)  # get方法加入请求头

    print(res)  # ,如果为40x则请求失败
    print(res.text)  # 显示网页所有内容和F12结果一样
except (ConnectionRefusedError, requests.exceptions.ConnectionError):  # 出现错误会显示一下内容
    print('拒绝连接')

当程序出现异常后,就不会直接报错,而是给一个提示,不会影响原来代码的运行。

BeautifulSoup库

可以轻松的解析Requests库请求的网页,并把网页源代码解析为Soup文档,以便过滤提取数据。

import requests
from bs4 import BeautifulSoup

headers = {
    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.77 Safari/537.36"
}

try:
    res = requests.get(url='https://blog.starmeow.cn/blog/1/detail/', headers=headers)  # get方法加入请求头

    soup = BeautifulSoup(res.text, 'html.parser')
    print(soup.prettify())
except requests.exceptions.ConnectionError:  # 出现错误会显示一下内容
    print('拒绝连接')

看上去与res.text结果类似,但通过BeautifulSoup库解析得到的soup文档按照标准缩进格式的结构输出,为结构化的数据,为数据的过滤提取做好准备。

BeautifulSoup库的主要解析器

解析器 使用方法 优势 劣势
Python标准库 BeautifulSoup(markup, “html.parser”) - Python的内置标准库- 执行速度适中- 文档容错能力强 Python 2.7.3 or 3.2.2)前 的版本中文档容错能力差
lxml HTML 解析器 BeautifulSoup(markup, “lxml”) - 速度快- 文档容错能力强 需要安装C语言库
lxml XML 解析器 BeautifulSoup(markup, [“lxml”, “xml”])BeautifulSoup(markup, “xml”) - 速度快- 唯一支持XML的解析器 需要安装C语言库
html5lib BeautifulSoup(markup, “html5lib”) - 最好的容错性- 以浏览器的方式解析文档- 生成HTML5格式的文档 速度慢,不依赖外部扩展

BeautifulSoup库官方推荐使用lxml作为解析器,效率更高。

标准选择器

  • find_all( name , attrs , recursive , text , **kwargs ) :可根据标签名、属性、内容查找文档
  • find( name , attrs , recursive , text , **kwargs ) :find返回单个元素,find_all返回所有元素
  • 其他

find_all()方法

import requests
from bs4 import BeautifulSoup

headers = {
    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.77 Safari/537.36"
}

try:
    res = requests.get(url='https://blog.starmeow.cn/blog/1/detail/', headers=headers)  # get方法加入请求头

    soup = BeautifulSoup(res.text, 'html.parser')
    # print(soup.prettify())
    find_widget = soup.find_all('div', 'widget')  # 查找class="widget"的所有div标签,最终结果以list方式展示
    print(find_widget)
    find_thumbnail = soup.find_all('span', attrs={'class': 'thumbnail'})  # attrs参数定义一个字典参数来所有包含特殊属性的tag,包含thumbnail的class的所有span
    print(find_thumbnail)
except requests.exceptions.ConnectionError:  # 出现错误会显示一下内容
    print('拒绝连接')

find()方法

find()方法与find_all()方法类似,只是find_all()方法返回时文档汇总复核条件的所有tag,是一个集合(),find()方法返回的是一个Tag对象(

select()方法

从浏览器选择功能Copy selector,得到的结果用户信息的获取。

import requests
from bs4 import BeautifulSoup

headers = {
    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.77 Safari/537.36"
}

try:
    res = requests.get(url='https://blog.starmeow.cn/blog/1/detail/', headers=headers)  # get方法加入请求头
    soup = BeautifulSoup(res.text, 'html.parser')
    title = soup.select("body > section > div > div > header > h1 > a")  # 右键Elements位置---Copy---Copy selector
    print(title)  # [创建项目初始化]

    hot_titles = soup.select("body > section > aside > div.widget.widget_hot > ul > li > a > span.text")  # li:nth-child(1)运行会报错,需改为li
    print(hot_titles)
    """
    复制结果:
    body > section > aside > div.widget.widget_hot > ul > li:nth-child(1) > a > span.text
    结果会报错,需要进行修改
    body > section > aside > div.widget.widget_hot > ul > li:nth-of-type(1) > a > span.text  得到第一个数据
    body > section > aside > div.widget.widget_hot > ul > li > a > span.text  得到所有数据
    """
    for title in hot_titles:
        print(title, title.get_text(), title.get_text() == title.text)
        """
        【Flask微电影】05.搭建前台页面-会员登录注册和会员中心 【Flask微电影】05.搭建前台页面-会员登录注册和会员中心 True
        【Flask微电影】10.搭建后台页面-会员管理、评论管理 【Flask微电影】10.搭建后台页面-会员管理、评论管理 True
        【Flask微电影】11.搭建后台页面-收藏管理、日志管理 【Flask微电影】11.搭建后台页面-收藏管理、日志管理 True
        【Flask微电影】07.搭建后台页面-后台登陆、后台主页页面 【Flask微电影】07.搭建后台页面-后台登陆、后台主页页面 True
        【Flask微电影】01.环境搭建项目目录分析 【Flask微电影】01.环境搭建项目目录分析 True
        """
except requests.exceptions.ConnectionError:  # 出现错误会显示一下内容
    print('拒绝连接')

使用title.get_text()或者title.text可以得到标签的文字信息

Lxml库

基于libxml2虚怀若谷人XML解析库的Python封装,使用C语言编写,解析速度比BeautifulSoup快。

实例1:爬取成都地区短租房信息

思路分析

访问:https://cd.xiaozhu.com/ 查看网页的信息

分页url地址:

  • https://cd.xiaozhu.com/ 或者 https://cd.xiaozhu.com/search-duanzufang-p1-0/
  • https://cd.xiaozhu.com/search-duanzufang-p2-0/
  • https://cd.xiaozhu.com/search-duanzufang-p3-0/
  • https://cd.xiaozhu.com/search-duanzufang-p4-0/

根据url构造,只需要改变p后面的数字,可以构造出所有页面网址。然后根据这些url进入到详情页,获取标题、地址、价格、房东等信息。

爬虫代码和注释

import requests
from bs4 import BeautifulSoup
import time

headers = {
    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.77 Safari/537.36"
}  # 用于伪装浏览器,便于爬虫的稳定性


def get_all_links(url):
    """
    得到每个分页的房子url列表
    :param url: 房源列表的分页url
    :return: 得到每一页的所有房源列表
    """
    html_data = requests.get(url=url, headers=headers)
    soup = BeautifulSoup(html_data.text, 'lxml')
    # print(soup)
    links = soup.select('#page_list > ul > li > a')
    res = list()
    for link in links:
        # print(link.get('href'))
        href = link.get('href')  # 获取标签href属性信息,得到进入详情页的URL
        res.append(href)
        time.sleep(2)
    return res


def get_info(url):
    """
    每一个房源信息获取
    :param url: 房源url
    :return: 房源字典信息
    """
    html_data = requests.get(url, headers=headers)
    soup = BeautifulSoup(html_data.text, 'lxml')
    titles = soup.select('div.wrap.clearfix.con_bg > div.con_l > div.pho_info > h4 > em')
    # 复制的结果是:body > div.wrap.clearfix.con_bg > div.con_l > div.pho_info > h4 > em,无法获取,需要去掉body
    title = ''
    if titles:
        title = titles[0].get_text().strip()  # 如果得到的结果不为空,取第一个的值
    addresses = soup.select('div.wrap.clearfix.con_bg > div.con_l > div.pho_info > p > span')
    # print(addresses)
    address = ''
    if addresses:
        address = addresses[0].get_text().strip()
    prices = soup.select('#pricePart > div.day_l > span')
    price = ''
    if prices:
        price = prices[0].get_text()
    images = soup.select('#curBigImage')
    image = ''
    if images:
        image = images[0].get('src')
    names = soup.select('#floatRightBox > div.js_box.clearfix > div.w_240 > h6 > a')
    name = ''
    if names:
        name = names[0].get_text()
    sexs = soup.select('#floatRightBox > div.js_box.clearfix > div.member_pic > div')
    sex = ''
    if sexs:
        sex = sexs[0].get('class')
    # print(sex)  # ['member_ico1']
    if "member_ico1" in sex:  # 根据class来判断房东的性别,男房东的class="member_ico"
        sex = '女'
    else:
        sex = '男'
    data = {
        'title': title,
        'address': address,
        'price': price,
        'image': image,
        'name': name,
        'sex': sex
    }
    # print(data)
    return data


if __name__ == '__main__':
    # fangzi_list = get_all_links('https://cd.xiaozhu.com/')
    # fangzi_info = get_info('https://cd.xiaozhu.com/fangzi/2148542359.html')
    page_urls = ['https://cd.xiaozhu.com/search-duanzufang-p{}-0/'.format(num) for num in range(1, 4)]  # 准备3页数据
    for page_url in page_urls:
        fangzi_list = get_all_links(page_url)  # 得到每一页的所有房源列表
        time.sleep(2)  # 暂停2秒,防止请求过快导致被服务器拒绝
        for fangzi in fangzi_list:
            print(get_info(fangzi))  # 遍历房源列表,得到每个房源信息

# 运行结果
# {'title': '宽窄巷子人民公园双地铁2号4号景区房熊猫风', 'address': '四川省成都市青羊区通惠门69号长富新城2栋24楼', 'price': '188', 'image': 'https://image.xiaozhustatic1.com/00,800,533/14,0,39,25214,1800,1200,71ccb694.jpg', 'name': '周周小家', 'sex': '男'}
# {'title': '地铁口春熙路太古里,熊猫基地大二居3床住5人', 'address': '四川省成都市成华区前锋路6号阳光欣园', 'price': '328', 'image': 'https://image.xiaozhustatic1.com/00,800,533/13,0,61,31508,1798,1200,d0c0cd6e.jpg', 'name': '幺妹家', 'sex': '女'}

实例2:爬取库存TOP500数据

思路分析

TOP500第一页链接:http://www.kugou.com/yy/rank/home/1-8888.html?from=rank

网页版不能手动翻页,但观察第一页的URL,将1改为2:http://www.kugou.com/yy/rank/home/2-8888.html?from=rank

正好是第二页的列表,浏览发现,每一页显示22条数据,所以500条,23页能显示完

需要爬取的信息有排名、歌手、歌曲、歌曲时长

爬虫代码和注释

import requests
from bs4 import BeautifulSoup
import time

headers = {
    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.77 Safari/537.36"
}  # 用于伪装浏览器,便于爬虫的稳定性


def get_info(url):
    html_data = requests.get(url=url, headers=headers)
    soup = BeautifulSoup(html_data.text, 'lxml')
    ranks = soup.select('#rankWrap > div.pc_temp_songlist > ul > li > span.pc_temp_num')  # 前3个数字加粗,select不能包含strong标签
    # print(ranks[0].get_text().strip(), ranks[4].get_text().strip())  # 获取歌曲的信息
    songs = soup.select('#rankWrap > div.pc_temp_songlist > ul > li > a')
    length_of_times = soup.select('#rankWrap > div.pc_temp_songlist > ul > li > span.pc_temp_tips_r > span')

    for rank, song, length_of_time in zip(ranks, songs, length_of_times):
        data = {
            'rank': rank.get_text().strip(),  # 排名
            'singer': song.get_text().split('-')[0].strip(),  # 歌手
            'name': song.get_text().split('-')[1].strip(),  # 歌曲名
            'url': song.get('href'),  # 歌曲链接
            'length_of_time': length_of_time.get_text().strip()  # 时长
        }
        print(data)
        return data


if __name__ == '__main__':
    get_info('http://www.kugou.com/yy/rank/home/1-8888.html?from=rank')
    top500_list = ['http://www.kugou.com/yy/rank/home/{}-8888.html?from=rank'.format(num) for num in range(1, 3)]  # range(1, 24)显示所有页
    for url in top500_list:
        get_info(url)
        time.sleep(2)


# 运行结果
# {'rank': '1', 'singer': '展展与罗罗', 'name': '沙漠骆驼', 'url': 'http://www.kugou.com/song/g3d726a.html', 'length_of_time': '5:38'}
# {'rank': '2', 'singer': '黑龙', 'name': '38度6', 'url': 'http://www.kugou.com/song/ootaieb.html', 'length_of_time': '3:11'}
# {'rank': '3', 'singer': '张紫豪', 'name': '可不可以', 'url': 'http://www.kugou.com/song/mkt6v7f.html', 'length_of_time': '4:00'}
# {'rank': '4', 'singer': 'G.E.M.邓紫棋', 'name': '光年之外', 'url': 'http://www.kugou.com/song/eoo8m01.html', 'length_of_time': '3:55'}
# {'rank': '5', 'singer': '半阳', 'name': '流浪', 'url': 'http://www.kugou.com/song/ndiin31.html', 'length_of_time': '3:39'}

zip()函数使用

描述

zip() 函数用于将可迭代对象作为参数,将对象中对应的元素打包成一个个元组,然后返回由这些元组组成的对象。

如果各个可迭代对象的元素个数不一致,则返回的对象长度与最短的可迭代对象相同。

利用 * 号操作符,与zip相反,进行解压。

语法

zip() 函数语法:

zip(iterable1,iterable2, ...)

参数说明:

  • iterable -- 一个或多个可迭代对象(字符串、列表、元祖、字典)

返回的是一个对象,如果想要得到列表,可以用 list() 函数进行转换。

实例

>>> a = [1,2,3] #此处可迭代对象为列表
>>> b = [4,5,6]
>>> c = [4,5,6,7,8]
>>> zipped = zip(a,b)
>>> zipped
#  #返回的是一个对象
>>> list(zipped)
# [(1, 4), (2, 5), (3, 6)] #使用list()函数转换为列表
>>> list(zip(a,c))
# [(1, 4), (2, 5), (3, 6)]
>>> zipped = zip(a,b)
>>> list(zip(*zipped)) #解压也使用list进行转换
# [(1, 2, 3), (4, 5, 6)]
#v1,v2,v3可是是任何可迭代对象,如:字符串、列表、元祖、字典
v1 = {1:11,2:22} #此处可迭代对象为字典
v2 = {3:33,4:44}
v3 = {5:55,6:66}

v = zip(v1,v2,v3)   #压缩
print(list(v))

w = zip(*zip(v1,v2,v3)) #解压
print(list(w))

# [(1, 3, 5), (2, 4, 6)]
# [(1, 2), (3, 4), (5, 6)]

搭配for循环支持并行迭代

list1 = [2,3,4]
list2 = [4,5,6]

for x,y in zip(list1,list2):
    print(x,y,'--',x*y)

# 2 4 -- 8
# 3 5 -- 15
# 4 6 -- 24

你可能感兴趣的:(【Python网络爬虫】01.爬虫原理,爬虫常用库入门练习)