手把手学爬虫第一弹——数据获取和解析

  • 2.简单POST请求

  • 3.复杂的网络请求

    • (1). 添加请求头headers
  • (2). 验证Cookies

  • (3). 会话请求

  • (4). 验证请求

  • (5). 请求超时与异常捕获

  • 4.代理服务

    • (1). 在代码中写入多个ip
  • (2). 获取免费ip存储后使用

      • a. 获取ip
  • b. 读取ip并判断是否可用

  • c.通过专门的API接口获取ip

  • 二、解析数据

    • 1. 正则表达式
    • (1). 正则表达式基础
      • a. 行定位符
  • b.元字符

  • c. 限定符

  • d.字符类

  • e. 排除字符

  • f. 选择字符

  • g. 转义字符

  • h. python中的正则表达式

  • (2). match() 匹配

  • (3). search() 匹配

  • (4). findall()匹配

  • (5). 字符串处理

  • 2. Xpath解析

  • 3. BeautifulSoup解析

    • (1). BeautifulSoup的简单应用
  • (2). 获取节点内容

  • (3). 方法获取内容

  • (4). CSS选择器

  • 三、爬虫项目实战

  • 总结


前言

==============================================================

Python作为一门人尽皆知的编程语言,其适用范围广泛,今天我就带着大家一起快速入门Python的爬虫,本文我们主要以requests第三方模块的请求为主,其他urllib或者是urllib自行了解,另外我也会带着大家一起对获取到的数据利用正则(re)、xpath、BeautifulSoup进行解析。文章篇幅较长,请耐心看完呦

学爬虫不要忘了爬虫祖师爷,有问题可以去他的网站看看。崔庆才个人站点


一、爬虫是什么?

====================================================================

爬虫,顾名思义,利用代码代替人手动获取网络上的信息的操作,爬虫加快了我们获取互联网海量信息的速度。

二、爬取数据(resquests模块)

===============================================================================

1.简单GET请求


对于部分网页我们只需要通过get请求就可以获取到网页信息,对于这部分网页我们只需要使用requests的get请求即可获取到网页信息,下面以百度首页为例:

import requests #导入requests模块

response = requests.get(‘https://www.baidu.com’) #发起get请求

result = response.content.decode(‘utf-8’) #对请求内容进行编码

print(‘响应状态码:’, response.status_code) #打印响应状态码

print(‘请求内容为:’, result) #打印请求结果

请添加图片描述


2.简单POST请求


post请求方式也叫作提交表单,表单中的数据就是请求参数。请求参数可以是列表、元组或者是JSON格式。

import requests # 导入模块

import json

data = { # 请求参数

‘1’: ‘能力是有限的,而努力是无限的。’,

‘2’: ‘星光不问赶路人,时光不负有心人。’

}

response = requests.post(‘http://httpbin.org/post’, data=data) # 发起请求

result = json.loads(response.text) # 数据格式化

print(result)

请添加图片描述


3.复杂的网络请求


(1). 添加请求头headers

部分网页为了保护数据会对访问者身份进行校验,如果我们的程序代码直接发起请求的话会被服务器拒绝访问,针对这种情况我们需要为我们的请求添加请求头,这样就可以对我们的请求进行伪装,从而成功访问网页内容。

import requests # 导入模块

import json

url = ‘https://www.baidu.com’

headers = {‘User-Agent’: ‘Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.159 Safari/537.36’}

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

result = response.content.decode(‘utf-8’)

print(‘响应状态码:’, response.status_code)

print(‘请求内容为:’, result)

请添加图片描述


(2). 验证Cookies

有些需要登录的网页通常会通过Cookies进行登录验证,对于这类网页我们需要获取到登录时的Cookies,这样就可以获取到登录后才可以查看的数据了,下面我们以豆瓣为例。

import requests

url = “https://www.douban.com/”

cookies = ‘ll=“118254”; bid=zIWp62M93rc; apiKey=; __utma=30149280.1537777336.1631262226.1631262226.1631262226.1; __utmc=30149280; __utmz=30149280.1631262226.1.1.utmcsr=baidu|utmccn=(organic)|utmcmd=organic; __utmt=1; user_data={“area_code”:“+86”,“number”:“15071579625”,“code”:“8567”}; last_login_way=phone; ap_v=0,6.0; __gads=ID=9daaafc881690f95:T=1631262281:S=ALNI_MaICBMAwA7Z3TFZJ92H_KR2rzdLpA; push_noty_num=0; push_doumail_num=0; __utmv=30149280.23665; __utmb=30149280.6.10.1631262226; vtoken=phone_reset_password 9c00c1cb111d496eb620336328857dd6; _pk_id.100001.2fad=d0329b8128734456.1631262400.1.1631262400.1631262400.; _pk_ses.100001.2fad=*; login_start_time=1631262407870’

headers = {

“User-Agent”: “Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.100 Safari/537.36”

}

创建RequestsCookieJar对象,用于设置Cookies信息

cookies_jar = requests.cookies.RequestsCookieJar()

for cookie in cookies.split(‘;’):

key, value = cookie.split(‘=’, 1)

cookies_jar.set(key, value)

发起请求

response = requests.get(url, headers=headers, cookies=cookies_jar)

result = response.text

print(‘响应状态码:’, response.status_code)

print(‘请求内容为:’, result)

请添加图片描述


(3). 会话请求

前面我们利用Cookies实现了模拟登陆,但这样不仅操作麻烦,而且部分网页的Cookies会有时间限制,一段时间以后Cookies就会过期,为了解决这一问题我们可以使用requests提供的session对象。

import requests # 导入模块

s = requests.Session() # 创建一个会话对象

data = {‘username’: ‘mrsoft’, ‘password’: ‘mrsoft’}

response_1 = s.post(‘http://site2.rjkflm.com:666/index/index/chklogin.html’, data=data) # 发送登录请求

response_2 = s.get(‘http://site2.rjkflm.com:666’) # 获取登陆后的页面

print(‘登录信息:’, response_1.text)

print(‘登录后页面:’, response_2.text)

请添加图片描述


(4). 验证请求

我们在访问某些页面的时候会弹出验证,要求我们输入用户名和密码,这时就可以使用requests自带的验证功能,只需要在请求方法中填写auth参数,该参数的值是一个HTTPBasicAuth对象。

import requests # 导入模块

from requests.auth import HTTPBasicAuth # 导入HTTPBasicAuth类

url = ‘http://site2.rjkflm.com:666/spider/auth/’

auth = HTTPBasicAuth(‘admin’, ‘admin’)

response = requests.get(url=url, auth=auth)

print(response.text)

请添加图片描述


(5). 请求超时与异常捕获

我们在访问一个网页时可能会由于网络原因或者是服务器原因导致请求超时或者产生异常,这时候我们就可以为请求设置超时时间和异常捕获。

import requests # 导入模块

try:

url = ‘https://www.baidu.com’

response = requests.get(url=url, timeout=0.01) # 超时时间为0.01秒

print(‘响应状态码:’, response.status_code)

except Exception as e:

print(‘异常为:’, str(e))

请添加图片描述


4.代理服务


在爬取一些网页时我们肯那个会遇到一种情况,频繁访问后网页无法继续爬取了,这时候一般是我们的行为被服务器认定为恶意爬取,对我们的访问ip进行了屏蔽。针对于这种情况我们需要采用代理IP的形式访问。

(1). 在代码中写入多个ip

import requests # 导入网络请求模块

头部信息

headers = {‘User-Agent’: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) ’

'AppleWebKit/537.36 (KHTML, like Gecko) ’

‘Chrome/72.0.3626.121 Safari/537.36’}

proxy = {‘http’: ‘http://117.88.176.38:3000’,

‘https’: ‘https://117.88.176.38:3000’} # 设置代理ip与对应的端口号

try:

对需要爬取的网页发送请求,verify=False不验证服务器的SSL证书

response = requests.get(‘http://2020.ip138.com’, headers=headers, proxies=proxy, verify=False, timeout=3)

print(response.status_code) # 打印响应状态码

except Exception as e:

print(‘错误异常信息为:’,e) # 打印异常信息

请添加图片描述


(2). 获取免费ip存储后使用

出现上面的情况多半是我们选择的ip为无效ip,针对这种情况我们一般会统一获取免费的代理ip,然后保存至文件,每次请求时都换一个新的IP.

a. 获取ip

import requests # 导入网络请求模块

from lxml import etree # 导入HTML解析模块

import pandas as pd # 导入pandas模块

import time

头部信息

headers = {‘User-Agent’: ‘Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.159 Safari/537.36’}

ip_list = []

ip_table = pd.DataFrame(columns=[‘ip’]) # 创建临时表格数据

for i in range(1, 11):

print(“正在爬取第{}页”.format(i))

url = ‘http://www.ip3366.net/?stype=1&page={}’.format(i)

发送网络请求

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

response.encoding = ‘gb2312’ # 设置编码方式

if response.status_code == 200: # 判断请求是否成功

html = etree.HTML(response.text) # 解析HTML

ip = html.xpath(‘//*[@id=“list”]/table/tbody/tr/td[1]/text()’) # 获取ip内容

port = html.xpath(‘//*[@id=“list”]/table/tbody/tr/td[2]/text()’) # 获取端口号

for j in range(0, 10):

my_ip = ip[j] + ‘:’ + port[j]

print(‘代理ip为:’, ip[j], ‘对应端口为:’, port[j])

ip_list.append(my_ip)

time.sleep(1)

ip_table[‘ip’] = ip_list # 将提取的ip保存至excel文件中的ip列

生成xlsx文件

ip_table.to_excel(‘E:/python/pythonProject3/venv/Include/ip.xlsx’, sheet_name=‘data’)

b. 读取ip并判断是否可用

这种方式存在的问题就是免费网站获取的ip有国内的有国外的,有可用的有不可用的,所以存在ip是失效的问题,解决办法就是掏钱买专门的api接口。

import time

import requests # 导入网络请求模块

import pandas # 导入pandas模块

from lxml import etree # 导入HTML解析模块

ip_table = pandas.read_excel(‘E:/python/pythonProject3/venv/Include/ip.xlsx’) # 读取代理IP文件内容

ip = ip_table[‘ip’] # 获取代理ip列信息

头部信息

headers = {‘User-Agent’: ‘Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.159 Safari/537.36’,

‘Accept-Language’: ‘zh-CN,zh;q=0.9’}

循环遍历代理IP并通过代理发送网络请求

for i in ip:

proxies = {‘http’: ‘http://{}’.format(i),

‘https’: ‘https://{}’.format(i)}

try:

verify=False不验证服务器的SSL证书

response = requests.get(‘http://2021.ip138.com/’, headers=headers, proxies=proxies, verify=False, timeout=10)

if response.status_code == 200: # 判断请求是否成功,请求成功说明代理IP可用

response.encoding = ‘utf-8’ # 进行编码

html = etree.HTML(response.text) # 解析HTML

info_1 = str(html.xpath(‘/html/body/p[1]/text()[1]’)).replace(“['\n”, “”).replace(“[']”, “”)

info_2 = str(html.xpath(‘/html/body/p[1]/a/text()’)).replace(“['”, “”).replace(“']”, “”)

info_3 = str(html.xpath(‘/html/body/p[1]/text()[2]’)).replace(“[‘] “, “”).replace(”\n’]”, “”)

print(info_1 + info_2 + info_3) # 输出当前ip匿名信息

time.sleep(3)

except Exception as e:

pass

print(‘错误异常信息为:’, e) # 打印异常信息

c.通过专门的API接口获取ip

import requests # 导入网络请求模块

from lxml import etree # 导入HTML解析模块

import time

url = ‘api接口’

headers = {‘User-Agent’: ‘Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.159 Safari/537.36’}

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

ip = str(response.text).replace(“\r”, “”).replace(“\n”, “”)

proxies = {‘http’: ‘http://{}’.format(ip),

‘https’: ‘https://{}’.format(ip)}

print(proxies)

try:

verify=False不验证服务器的SSL证书

response = requests.get(‘http://2021.ip138.com/’, headers=headers, proxies=proxies, verify=False, timeout=10)

if response.status_code == 200: # 判断请求是否成功,请求成功说明代理IP可用

response.encoding = ‘utf-8’ # 进行编码

html = etree.HTML(response.text) # 解析HTML

info_1 = str(html.xpath(‘/html/body/p[1]/text()[1]’)).replace(“['\n”, “”).replace(“[']”, “”)

info_2 = str(html.xpath(‘/html/body/p[1]/a/text()’)).replace(“['”, “”).replace(“']”, “”)

info_3 = str(html.xpath(‘/html/body/p[1]/text()[2]’)).replace(“[‘] “, “”).replace(”\n’]”, “”)

print(info_1 + info_2 + info_3) # 输出当前ip匿名信息

time.sleep(3)

except Exception as e:

pass

print(‘错误异常信息为:’, e) # 打印异常信息

请添加图片描述


二、解析数据

==================================================================

当我们使用爬虫的时候大多数是为了爬取我们需要的部分数据,但直接获取到的往往不是我们需要的,这时候就需要我们对于爬取到的数据进行解析,进而在数据中找到我们需要的数据,接下来我将和大家一起使用正则(re)、Xpath、Beautiful Soup进行数据解析工作。

1. 正则表达式


正则表达式顾名思义就是由字符组成的表达式,这些表达式根据不同的组合可以匹配字符串中需要的部分。

(1). 正则表达式基础

a. 行定位符

行定位符用于描述字符串的边界。

| 符号 | 作用 |

| — | — |

| ^ | 表示行的开始 |

| $ | 表示行的结尾 |

b.元字符

元字符使用:

\bmr\w*\b

\b表示单词的边界

mr表示匹配开头是mr的字串

\e*表示匹配任意数量的字母或数字

该表达式可以匹配mrsoft、mrsbook、mr1234等字符串

| 代码 | 说明 |

| — | — |

| . | 匹配除换行符以外的任意字符 |

| \w | 匹配字母、数字、下划线、汉字 |

| \W | 匹配除了字母、数字、下划线、汉字以外的字符(与\w相反) |

| \s | 匹配任意空白符 |

| \S | 匹配除单个空白符(包括Tab和换行符)以外所有字符 |

| \d | 匹配数字 |

| \D | 匹配任意非数字 |

| \A | 从字符串开始处匹配 |

| \Z | 从字符串结束处匹配 |

| \b | 匹配一个单词的边界,单词分界符通常是空格、标点或者换行 |

| \B | 匹配非单词边界 |

| ^ | 匹配字符串的开始 |

| $ | 匹配字符串的结束 |

| () | 被括起来的表达式将作为分组 |

c. 限定符

上面提到"\w*"可以匹配任意数量的字母或数字。如果我们要匹配一定数量的数字,比如11位数的手机号?这时候就可以用限定符来实现。

^\d{11}$

匹配11位数的电话号码

| 符号 | 说明 | 举例 |

| — | — | — |

| ? | 匹配前面的字符零次或一次 | colour?r 可以匹配到colour和color |

| + | 匹配前面的字符一次或多次 | go+gle 可以匹配gogle到goooo…gle |

| * | 匹配前面的字符零次或多次 | go*gle 可以匹配ggle到goooo…gle |

| {n} | 匹配前面的字符串n次 | go{2}gle 只可以匹配google |

| {n,} | 匹配前面的字符最少n次 | go{2,}gle 可以匹配从google到goooo…gle |

| {n,m} | 匹配前面的字符最少n次,最多m次 | employe{0,2} 可以匹配employ、employe\employee |

d.字符类

假如我们要匹配所有大小写字母和数字,你会怎么做?列举所有的可能?显然不是,这时候我们可以使用正则表达式提供的字符类,将我们的条件放在中括号里面,例如:

[a-z0-9A-Z] # 可以匹配所有字母和数字

e. 排除字符

上面我们可以利用字符类获取我们想要的字符,那如何排除我们不需要的字符呢,很简单,在上面的表达式前面加一个^就可以了,例如:

[^a-zA-Z] # 可以匹配一个不是字母的字符

f. 选择字符

如果我们要在一堆字符里面找出所有的手机号码或者是身份证号码,如何运用正则表达式呢?分析一下身份证号码的组成,一共18位,前17位位数字,最后一位为数字或者是X,根据这一描述,我们显然可以得出如下的表达式:

[^\d{18}KaTeX parse error: Undefined control sequence: \d at position 4: |(^\̲d̲{17}(\d|X|x))

g. 转义字符

正则表达式的转义字符和python的转义字符基本没什么区别,例如当我们需要匹配的是个ip地址时,192.168.1.1中的.如何匹配呢?前面我们说到正则里面的点可以匹配一个任意字符,那这里就需要对其进行转义.

[1-9]{1,3}.[0-9]{1,3}.[0-9]{1,3}.[0-9]{1,3} # 匹配ip地址

h. python中的正则表达式

在python里面我们一般不会写模式字符串,即在转义的地方加上\,这样会导致表达式中有大量的\,取而代之的是原生表达式,在表达式前面加上 R 或 r.

(2). match() 匹配

match()从字符串的开始位置匹配,如果在起始位置匹配成功就直接返回结果,反之返回None.

re.match(‘正则表达式’, ‘待匹配字符串’, ‘修饰符’)

例如:

re.match(‘mr_\w+’, ‘MR_SHOPmr_shop’, re.I)

I 表示不区分大小写

| 表达式 | 匹配效果 | 匹配结果 |

| — | — | — |

| re.match(‘mr_\w+’, ‘MR_SHOPmr_shop’, re.I) | 匹配以指定字符串开头 | <re.Match object; span=(0, 7), match=‘MR_SHOP’> |

| re,match(“.ello”, “hello”) | 匹配任意开头的字符串 | <re.Match object; span=(0, 5), match=‘hello’> |

(3). search() 匹配

search()方法不同于match()方法,search()会在整个字符串搜索第一匹配的值,匹配成功就返回,否则返回None。

re,search(“mr_\w+”, “MR_SHOP”, re.I)

re,search(“mr_\w+”, “项目名称 MR_SHOP”, re.I)

两个表示匹配结果一样

| 表达式 | 匹配效果 |

| — | — |

| \d? | 匹配多个数字,可有可无 |

| \b | 表示字符串的边界,可以是开头、结尾、空格以及换行 |

(4). findall()匹配

findall()方法会搜索整个字符串寻找符合要求的字符,并以列表的形式返回,如果没有匹配到就会返回空列表。

| 表达式 | 匹配效果 |

| — | — |

| re.findall(‘mr_\w+’, ‘MR_SHOP mr_shop’, re.I) | 所有指定字符开头的字符串 |

| re.findall(‘https://(.*)/’, ‘http://www.hao123.com/’) | 贪婪匹配,获取//开始到/前面的所有字符 |

| re.findall(‘https://(.*?)/’, ‘http://www.hao123.com/’) | 非贪婪匹配,这样可能匹配不到任何字符,因为匹配结果会尽可能少 |

(5). 字符串处理

  • 替换字符串

re.sub(‘正则表达式’, ‘要替换的字符串’, ‘要被替换的字符串’, ‘替换的最大次数,默认为0’, 修饰符)

import re

str = r’1[34578]\d{9}’

string = ‘中奖号码为3867363546 联系电话为:15071567345’

result = re.sub(str, ‘1**********’, string)

print(result)

输出结果为: 中奖号码为3867363546 联系电话为:1**********

  • 分割字符串

re.split(‘正则表达式’, ‘要匹配的字符串’, 最大拆分次数, 修饰符)

import re

str = r’[?|&]’

url = ‘http://www.baidu.com?a=12&b=3’

result = re.split(str, url)

print(result)

输出结果为: [‘http://www.baidu.com’, ‘a=12’, ‘b=3’]

2. Xpath解析


上一节我们学了正则表达式清洗数据,这一节我们学习一种更加便捷的数据清洗解析方式,XPath。这是一种基于XML的路径语言。

Xpath常用的路径表示:

| 表达式 | 描述 |

| — | — |

| nodename | 此节点的所有子节点 |

| / | 从当前节点选取子节点 |

| // | 从当前节点选取子孙节点 |

| . | 选取当前节点 |

| … | 选取当前节点的父节点 |

| @ | 选取属性class |

| * | 选取所有节点 |

这种解析方式主要熟悉网页结构,利用上面的路径表达式选取对应的路径。浏览器也提供了直接复制的Xpath路径,使用如下:

请添加图片描述

这里我就不再详细介绍这种解析方法,大家可以参考这位博主的文章,写的很详细了!Xpath解析数据

请添加图片描述

from lxml import etree

import requests

url = “https://wuhan.zbj.com/search/f/?type=new&kw=%E5%B0%8F%E7%A8%8B%E5%BA%8F%E5%BC%80%E5%8F%91”

resp = requests.get(url)

xml = resp.text

tree = etree.HTML(xml)

res = tree.xpath(‘//div[@class=“service-info-wrap”]’)

print(res)

for item in res:

price = item.xpath(‘./div[@class=“service-price clearfix”]/span/text()’)

title = item.xpath(‘./div[@class=“service-title”]/p/text()’)

result = {

“price”: ‘’.join(price),

“title”: ‘’.join(title)

}

print(result)

一、Python所有方向的学习路线

Python所有方向路线就是把Python常用的技术点做整理,形成各个领域的知识点汇总,它的用处就在于,你可以按照上面的知识点去找对应的学习资源,保证自己学得较为全面。

二、学习软件

工欲善其事必先利其器。学习Python常用的开发软件都在这里了,给大家节省了很多时间。

三、入门学习视频

我们在看视频学习的时候,不能光动眼动脑不动手,比较科学的学习方法是在理解之后运用它们,这时候练手项目就很适合了。

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化学习资料的朋友,可以戳这里无偿获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

  • 30
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值