爬虫知识点梳理

一、爬虫介绍

(一)爬虫的分类

  1、通用爬虫:通用爬虫是搜索引擎(Baidu、Google、Yahoo等)“抓取系统”的重要组成部分。主要目的是将互联网上的网页下载到本地,形成一个互联网内容的镜像备份。再对这些网页做相关处理(提取关键字、去掉广告),最后提供一个用户检索接口。 

搜索引擎如何抓取互联网上的网站数据?

  • 门户网站主动向搜索引擎公司提供其网站的url
  • 搜索引擎公司与DNS服务商合作,获取网站的url
  • 门户网站主动挂靠在一些知名网站的友情链接中

 2、聚焦爬虫:聚焦爬虫是根据指定的需求抓取网络上指定的数据。例如:获取豆瓣上电影的名称和影评,而不是获取整张页面中所有的数据值。

(二)Jupyter Notebook安装

1、Jupyter Notebook介绍

Jupyter Notebook是基于网页的用于交互计算的应用程序,可以在网页中直接编写代码、运行代码等。

官网:https://jupyter.org/

2、安装

注:安装Python3.3及以上版本或2.7版本

扫描二维码关注公众号,回复: 5592363 查看本文章

需要使用Anaconda安装:

(1)去官网下载anaconda:https://www.anaconda.com/distribution/  根据不同操作系统下载对应版本进行安装即可

配置环境变量后,可以通过命令启动:

jupyter notebook

进入如下界面:

你从哪个目录输入命令进到页面的,这个页面的主目录就是你输入命令的目录。

(2)使用介绍

目录下新建文件夹

勾选刚刚新建的文件夹,可以对其进行重命名:

点击进入这个文件夹,可以新建文件,如新建一个文本文件:

然后编写代码即可。

创建Python3源文件:

重命名后到目录中查看文件:

之后就可以在这里面进行爬虫项目编写。

(3)编写代码

执行标记类型的cell后将生成一个不可编辑的文档,如果想要继续编辑,就选中当前cell进行的双击即可:

效果:

标记类型的cell主要用于编写文档、HTML文件等 。

 (4)常用快捷键

向上插入一个cell:a

向下插入一个cell:b

将cell类型切换为Markdown类型:m

将cell类型切换为code类型:y 

执行cell:shift+enter

查看函数或模块的帮助文档:shift+tab

自动补全:tab

二、模块

(一)urllib模块

1、介绍

urllib是Python自带的一个用于爬虫的库,主要作用就是可以通过代码模拟浏览器发送请求。

常被用到的子模块在Python3中为 urllib.request 和 urllib.parse,在Python2中是 urllib 和 urllib2 。

使用流程:

  • 指定url
  • 基于urllib的request子模块发起请求
  • 获取响应中的数据值
  • 持久化存储

2、简单的爬取示例

  • 爬取搜狗首页数据:

import urllib

# 1、指定URL
url = "https://www.sogou.com/"

# 2、发送请求,获取响应对象
response = urllib.request.urlopen(url=url)

# 3、获取页面数据
page_data = response.read() # read()返回的是byte类型的数据
# print(page_data)

# 4、持久化数据
with open("./sougou.html", "wb") as f:
    f.write(page_data)
    print("successfully!")
View Code
  • 爬取指定词条对应的页面数据
import urllib

# 注意:url中不可以存在非ascii码的字符数据,在发送请求之前要对URL中的非ascii码的字符数据进行转码
query_data = urllib.parse.quote("爬虫")

url = "https://www.sogou.com/web?query=%s" % query_data
response = urllib.request.urlopen(url=url)
page_data = response.read()

with open("爬虫.html", "wb") as f:
    f.write(page_data)
View Code

3、反爬机制--UA身份伪装

User-Agent(UA):请求载体的身份标识

网站会检查UA,如果发现这个UA不是正常的浏览器发起的,则可能是个爬虫程序,那么网站可能就会拒绝提供数据。

相应的反反爬机制:伪装爬虫程序请求的UA

import urllib

url = "https://www.baidu.com/"

# 构造请求头相关信息
headers={"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.4325.121 Safari/694.36"}  

# 自定制请求对象
request = urllib.request.Request(url=url, headers=headers)

# 针对自定制的请求对象发起请求
response = urllib.request.urlopen(request)
print(response.read())
View Code

4、post请求

如何发起post请求爬取数据?如:在百度翻译中输入内容,获取翻译结果

先在浏览器中看一下,整个翻译过程的所有ajax请求中,哪个的请求头中携带了你所要翻译的文字:

可以在这个ajax请求里面看到有关请求信息:

接下来就可以模拟浏览器发起请求:

import urllib
import json

url = "https://fanyi.baidu.com/sug"

# 将post参数封装成字典
data = {"kw": "爬虫"}

# 编码请求数据进行编码处理
data = urllib.parse.urlencode(data)  # 得到一个str类型

# 将str类型转换为byte类型
data = data.encode()

# 发起post请求
response = urllib.request.urlopen(url=url, data=data)

json_data = response.read()
res = json.loads(json_data)
print(res)  # {'errno': 0, 'data': [{'k': '爬虫', 'v': '[pá chóng] reptile;'}]}
View Code

5、基于urllib模块的代理操作

代理,就是通过第三方代替本体处理相关事务,类似于代购,中介。

应用场景:一些网站会有相应的反爬虫措施,例如很多网站会检测某一段时间某个IP的访问次数,如果访问频率太快以至于看起来不像正常访客,它可能就会会禁止这个IP的访问。所以我们需要设置一些代理IP,每隔一段时间换一个代理IP,就算IP被禁止,依然可以换个IP继续爬取。

代理的分类:

  • 正向代理:代理客户端获取数据。正向代理是为了保护客户端防止被追究责任。
  • 反向代理:代理服务器提供数据。反向代理是为了保护服务器或负责负载均衡。
import urllib

# 创建处理器对象,在其内部封装代理IP和port
handler = urllib.request.ProxyHandler(proxies={"http": "192.168.23.233:8080"})
# 创建opener对象,使用该对象发起请求
opener = urllib.request.build_opener(handler)

url = "https://www.baidu.com/s?ie=UTF-8&wd=python"
headers = {
    'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.181 Safari/537.36',
}
request = urllib.request.Request(url=url, headers=headers)

response = opener.open(request)
with open("proxy_data.html", "wb")as f:
    f.write(response.read())
View Code

6、基于urllib模块的cookie

有些网站需要有服务器返回给浏览器的cookie信息才能够获取数据,否则服务器返回的可能就是个登录页面。

所以我在爬取某些网站数据时,往往通过以下步骤来完成:

(1)使用爬虫程序登录一次网站,获取响应数据中的cookie

(2)再使用url进行请求时,携带 (1)中的cookie,进而获取到想要的数据

可以使用cookiejar来获取cookie:

cookiejar对象:
    - 作用:自动保存请求中的cookie数据信息
    - 注意:必须和handler和opener一起使用
cookiejar使用流程:
    - 创建一个cookiejar对象
      import http.cookiejar
      cj = http.cookiejar.CookieJar()
    - 通过cookiejar创建一个handler
      handler = urllib.request.HTTPCookieProcessor(cj)
    - 根据handler创建一个opener
      opener = urllib.request.build_opener(handler)
    - 使用opener.open方法去发送请求,且将响应中的cookie存储到openner对象中,后续的请求如果使用openner发起,则请求中就会携带了cookie

示例:爬取人人网的个人信息页面

import urllib
from http import cookiejar

cj = cookiejar.CookieJar()  # 请求中的cookie会自动存储到cj对象中
handler = urllib.request.HTTPCookieProcessor(cj)
opener = urllib.request.build_opener(handler)

url = "http://www.renren.com/ajaxLogin/login?1=1&uniqueTimestamp=201873958471"
# 自定义一个请求对象,让该对象作为opener的open函数中的参数
data={
    "email":"[email protected]",
    "icode":"",
    "origURL":"http://www.renren.com/home",
    "domain":"renren.com",
    "key_id":"1",
    "captcha_type":"web_login",
    "password":"40dc65b82edd06d064b54a0fc6d202d8a58c4cb3d2942062f0f7dd128511fb9b",
    "rkey":"41b44b0d062d3ca23119bc8b58983104",
  
    "f":"https%3A%2F%2Fwww.baidu.com%2Flink%3Furl%3DpPKf2680yRLbbZMVdntJpyPGwrSk2BtpKlEaAuKFTsW%26wd%3D%26eqid%3Deee20f380002988c000000025b7cbb80"
}
data=urllib.parse.urlencode(data).encode()
request=urllib.request.Request(url,data=data)
opener.open(request)

#获取当前用户的二级子页面
s_url='http://www.renren.com/289676607/profile'
#该次请求中就携带了cookie
response=opener.open(s_url)
with open("renren.html", "wb")as f:
    f.write(response.read())
View Code

(二)requests模块

1、requests模块介绍

requests是Python中原生的基于网络请求的模块,用于模拟浏览器发起请求。

为什么使用requests模块?

  • 使用urllib模块时,需要我们手动处理编码问题
  • 使用urllib模块时,需要手动处理post请求参数
  • 使用urllib模块处理cookie和代理操作的时候步骤比较繁琐

requests模块安装与使用:

# 安装
pip install requests

#使用:
-指定URL
-使用requests模块发起请求
-获取响应数据
-持久化存储

2、基于requests模块的get请求

示例:

import requests

url = "https://www.sogou.com/"
response = requests.get(url=url)  # 返回请求成功后的响应对象
# text属性:获取响应对象中的字符串形式的页面数据
page_data = response.text

with open("sougou.html", "w", encoding="utf-8")as f:
    f.write(page_data)

response对象的其他属性:

import requests

url = "https://www.sogou.com/"
response = requests.get(url=url)  # 返回请求成功后的响应对象
# content属性:获取响应对象中的byte类型的页面数据
page_data = response.content
print(page_data)
import requests

url = "https://www.sogou.com/"
response = requests.get(url=url)  # 返回请求成功后的响应对象
# status_code属性:获取响应对象中的状态码
page_data = response.status_code
print(page_data)
import requests

url = "https://www.sogou.com/"
response = requests.get(url=url)  # 返回请求成功后的响应对象
# headers属性:获取响应对象中的响应头信息 一个字典
page_data = response.headers
print(page_data)
import requests

url = "https://www.sogou.com/"
response = requests.get(url=url)  # 返回请求成功后的响应对象
# url属性:获取请求的URL
page_data = response.url
print(page_data)

携带参数的get请求:

# 方式一:

import
requests url = "https://www.sogou.com/web?query=爬虫&ie=utf8" response = requests.get(url=url) # 自动处理URL中编码 page_data = response.text print(page_data)
# 方式二:将请求参数封装到字典中

import requests

url = "https://www.sogou.com/web"
params = {"query": "爬虫"}
response = requests.get(url, params = params)
print(response.text)

自定义请求头信息:

import requests

url = "https://www.sogou.com/web"
params = {"query": "爬虫"}
headers = {"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3040.121 Safari/432.36"}
response = requests.get(url, params = params, headers=headers)
print(response.status_code)

3、基于requests模块的post请求

需求:只有登录成功后才能获取页面数据

import requests

url = "https://accounts.douban.com/login"  # 找到post请求所对应的URL
data = {
    "source": "movie",
    "redir": "https://movie.douban.com",
    "form_email": "15124567889",
    "form_password": "123456789",
    "login": "登录"
}
headers = {
    "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_4) AppleWebKit/323.36 (KHTML, like Gecko) Chrome/72.0.4232.121 Safari/537.36"
}
response = requests.post(url=url, data=data, headers=headers)
with open("douban.html", "wb")as f:
    f.write(response.content)
View Code

4、基于requests模块的ajax的get请求

# 获取豆瓣电影的分类信息
import requests

url = "https://movie.douban.com/j/new_search_subjects"
params = {
    "sort": "U",
    "range": [0,10],
    "tags": "",
    "start": "0",
    "countries": "香港"
}
headers = {
    "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.121 Safari/537.36"
}

response = requests.get(url=url, params=params, headers=headers)
page_data = response.text
print(page_data)
View Code

返回的结果是json字符串。

5、基于requests模块的ajax的post请求

# 查询肯德基的餐厅信息
import requests

url = "http://www.kfc.com.cn/kfccda/ashx/GetStoreList.ashx?op=keyword"
data = {
    "cname": "",
    "pid": "",
    "keyword": "上海",
    "pageIndex": "2",
    "pageSize": "10"
}
headers = {
    "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.121 Safari/537.36"
}
response = requests.post(url, headers=headers, data=data)

page_data = response.text  # 一组json字符串
print(page_data)
View Code

6、示例

# 爬取搜狗知乎某个词条对应一定页码范围的数据
import requests
import os

if not os.path.exists("./sougou_pages"):
    os.mkdir("./sougou_pages")

url = "https://zhihu.sogou.com/zhihu"

# 检索字段
words = input("输入要检索的词条:").strip()

# 动态指定页码范围
start_num = int(input("输入起始页码:"))
end_num = int(input("输入结束页码:"))

headers = {
    "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.121 Safari/537.36"
}

# 通过循环获取指定页码范围的数据
for i in range(start_num, end_num+1):
    params = {
        "query":words,
        "page": i,
        "ie": "utf8"
    }
    response = requests.get(url, params=params, headers=headers)
    page_text = response.text
    file_name = "sougou_page_data_%s.html" % str(i)  # 文件名
    file_path = os.path.join("./sougou_pages", file_name)  # 文件路径
    with open(file_path, "w", encoding="utf-8")as f:
        f.write(page_text)
        print("第%d页写入成功" % i)
View Code

爬取百度贴吧指定页码的数据

爬取维基百科指定页码的数据

7、requests模块的cookie操作

当我们点击“登录”按钮,登录成功之后,服务端会为该次请求创建一个cookie,并且将这个cookie发送给客户端,客户端将其进行存储

import requests

# 发起登录请求,将获取到的cookie存入session对象中
post_url = "https://accounts.douban.com/j/mobile/login/basic"
data = {
    "ck": "",
    "name": "15523455432",
    "password": "123456789",
    "remember": "false",
    "ticket": "5uFnV1iD7YieacEl8MnYS67-sMYiRMAw3qK42WWk29CgL0Ll2bNrfFRtR7N7of985bVPu17aUU4*"
}
headers = {
    "Referer": "https://accounts.douban.com/passport/login?source=movie",
    "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_4) AppleWebKit/342.36 (KHTML, like Gecko) Chrome/72.0.3626.121 Safari/537.36",
    "X-Requested-With": "XMLHttpRequest"
}
# 创建session对象
session = requests.session()  

# 登录请求:获取cookie
login_response = session.post(url=post_url, data=data, headers=headers)

# 二次请求携带cookie:获取数据
url = "https://www.douban.com/"
response = session.get(url, headers=headers)
page_text = response.text
with open("douban_personal.html", "w", encoding="utf-8")as f:
    f.write(page_text)
    print("successfully")
View Code

8、requests模块的代理操作

import requests

url = "https://www.baidu.com/s?ie=utf-8&wd=IP"

headers = {
    "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.121 Safari/537.36"
}

# 将代理IP封装到字字典中  代理IP要与实际IP的协议类型相同,比如都使用http,或都使用https
proxy_ip = {"https": "114.88.53.19:53281"}

# 更换网络IP
response = requests.get(url=url, headers=headers, proxies=proxy_ip)
page_text = response.text
with open("proxy_page.html", "w", encoding="utf-8")as f:
    f.write(page_text)
    print("successfully")
View Code

9、验证码处理

云打码平台自动识别验证码流程:

(1)对携带验证码的页面数据进行抓取

(2)解析页面数据中的验证码,将验证码图片下载到本地

(3)将验证码图片发送给第三方平台处理,然后返回图片上的数据

云打码平台http://www.yundama.com/demo.html 

注册:用户注册和开发者注册都要

登录:登录开发者平台

  • 示例代码下载(在开发者首页->开发文档->调用示例即最新DLL->PythonHTTP示例下载),将下载的压缩包解压,里面有三个文件
  • 新建一个软件(在开发者首页->我的软件->添加新软件->输入软件名称,点击提交即可

使用刚刚下载的示例代码中源码文件中的代码进行修改(YDMHTTPDemo3.x.py),让其识别验证码图片的数据值:

  • 拷贝YDMHTTPDemo3.x.py文件中的类到你的cell中,运行一下cell将类加载到当前源文件中
  • 拷贝YDMHTTPDemo3.x.py文件中余下的代码,新建一个cell,在里面新建一个函数,将拷贝的代码写到函数中,修改里面的用户名、密码等配置信息(记得以普通用户身份登录打码平台充值后才能进行验证码识别)
import http.client, mimetypes, urllib, json, time, requests


class YDMHttp:

    apiurl = 'http://api.yundama.com/api.php'
    username = ''
    password = ''
    appid = ''
    appkey = ''

    def __init__(self, username, password, appid, appkey):
        self.username = username  
        self.password = password
        self.appid = str(appid)
        self.appkey = appkey

    def request(self, fields, files=[]):
        response = self.post_url(self.apiurl, fields, files)
        response = json.loads(response)
        return response
    
    def balance(self):
        data = {'method': 'balance', 'username': self.username, 'password': self.password, 'appid': self.appid, 'appkey': self.appkey}
        response = self.request(data)
        if (response):
            if (response['ret'] and response['ret'] < 0):
                return response['ret']
            else:
                return response['balance']
        else:
            return -9001
    
    def login(self):
        data = {'method': 'login', 'username': self.username, 'password': self.password, 'appid': self.appid, 'appkey': self.appkey}
        response = self.request(data)
        if (response):
            if (response['ret'] and response['ret'] < 0):
                return response['ret']
            else:
                return response['uid']
        else:
            return -9001

    def upload(self, filename, codetype, timeout):
        data = {'method': 'upload', 'username': self.username, 'password': self.password, 'appid': self.appid, 'appkey': self.appkey, 'codetype': str(codetype), 'timeout': str(timeout)}
        file = {'file': filename}
        response = self.request(data, file)
        if (response):
            if (response['ret'] and response['ret'] < 0):
                return response['ret']
            else:
                return response['cid']
        else:
            return -9001

    def result(self, cid):
        data = {'method': 'result', 'username': self.username, 'password': self.password, 'appid': self.appid, 'appkey': self.appkey, 'cid': str(cid)}
        response = self.request(data)
        return response and response['text'] or ''

    def decode(self, filename, codetype, timeout):
        cid = self.upload(filename, codetype, timeout)
        if (cid > 0):
            for i in range(0, timeout):
                result = self.result(cid)
                if (result != ''):
                    return cid, result
                else:
                    time.sleep(1)
            return -3003, ''
        else:
            return cid, ''

    def report(self, cid):
        data = {'method': 'report', 'username': self.username, 'password': self.password, 'appid': self.appid, 'appkey': self.appkey, 'cid': str(cid), 'flag': '0'}
        response = self.request(data)
        if (response):
            return response['ret']
        else:
            return -9001

    def post_url(self, url, fields, files=[]):
        for key in files:
            files[key] = open(files[key], 'rb');
        res = requests.post(url, files=files, data=fields)
        return res.text


def get_code(code_img_path):
    """该函数调用了打码平台的相关接口,对指定验证码图片进行识别,返回图片上的数据"""
    # 云打码平台普通用户的用户名
    username    = '用户名'

    # 密码
    password    = '密码'                            

    # 软件ID(软件代码),开发者分成必要参数。登录开发者后台【我的软件】获得!
    appid       = ID                                     

    # 软件密钥,开发者分成必要参数。登录开发者后台【我的软件】获得!
    appkey      = '秘钥'    

    # 图片文件
    filename    = code_img_path                        

    # 验证码类型,# 例:1004表示4位字母数字,不同类型收费不同。请准确填写,否则影响识别率。在此查询所有类型 http://www.yundama.com/price.html
    codetype    = 1004

    # 超时时间,秒
    timeout     = 20                                    

    # 检查
    if (username == 'username'):
        print('请设置好相关参数再测试')
    else:
        # 初始化
        yundama = YDMHttp(username, password, appid, appkey)

        # 登陆云打码
        uid = yundama.login();
        print('uid: %s' % uid)

        # 查询余额
        balance = yundama.balance();
        print('balance: %s' % balance)

        # 开始识别,图片路径,验证码类型ID,超时时间(秒),识别结果
        cid, result = yundama.decode(filename, codetype, timeout);
        print('cid: %s, result: %s' % (cid, result))
    return result


import requests
from lxml import etree

# 获取带有验证码图片的页面
url = "http://www.cdpta.gov.cn/frt/student/login.do"
headers = {
    "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.121 Safari/537.36"
}
session = requests.session()
page_text = session.get(url, headers=headers).text

# 解析页面的验证码
tree = etree.HTML(page_text)

# 去网页中复制验证码图片的xpath 如果需要的话再通过相关处理获取到图片的URL 
code_img_url = tree.xpath('//*[@id="yzmimg"]/@src')[0]  
code_img_url = "http://www.cdpta.gov.cn" + code_img_url.split(";")[0]

# 获取图片的二进制数据
code_img = session.get(url=code_img_url, headers=headers).content  
# 保存图片到本地
with open("code_img.png", "wb")as f:
    f.write(code_img)
    print("successfully")

# 调用函数识别图片验证码
res = get_code("./code_img.png")
print("验证码字符:", res)

# 进行登录操作
login_url = "http://www.cdpta.gov.cn/frt/student/login.do"
data = {
    "name": "xxx",
    "idcard": "xxx",
    "password": "xxx",
    "yzm": res,
    "newpassword": "",
    "newpassword2": "",
    "photofile": "(binary)",
    "findpassword_mo": "将密码直接发送到注册的手机",
    "loginction": "登录"
}
response = session.post(url=login_url, data=data, headers=headers)
page_content = response.content
with open("cdrs.html", "wb")as f:
    f.write(page_content)
    print("successfully")
View Code

三、数据解析

一般在持久化存储爬取的数据之前会进行数据解析,留下有用的部分。

(一)正则解析

1、正则匹配规则:

单字符:
        . : 除换行以外所有字符
        [] :[aoe] [a-w] 匹配集合中任意一个字符
        \d :数字  [0-9]
        \D : 非数字
        \w :数字、字母、下划线、中文
        \W : 非\w
        \s :所有的空白字符包,括空格、制表符、换页符等等。等价于 [ \f\n\r\t\v]。
        \S : 非空白
    数量修饰:
        * : 任意多次  >=0
        + : 至少1次   >=1
        ? : 可有可无  0次或者1次
        {m} :固定m次 hello{3,}
        {m,} :至少m次
        {m,n} :m-n次
    边界:
        $ : 以某某结尾 
        ^ : 以某某开头
    分组:
        (ab)  
    贪婪模式: .*
    非贪婪(惰性)模式: .*?

    re.I : 忽略大小写
    re.M :多行匹配
    re.S :单行匹配

    re.sub(正则表达式, 替换内容, 字符串)
View Code

2、使用:

import re

# 提取python
s = "javapythongo+c#"
re.findall("python", s)  # 返回一个列表 ['python']

# 提取hello world
s = "<html><h3>hello world</h3><html>"
re.findall("<h3>(hello world)</h3>", s)[0]  # 利用分组

# 提取123
s = "哈哈哈123嘻嘻嘻"
re.findall("\d+", s)[0]

# 提取http:// 和 https://
s = "http://www.baidu.com and https://www.baidu.com and httpss://"
# re.findall("https?://", s)  # ?表示前面的字符出现0次或1次
re.findall("https{0,1}://", s)  # {}表示前面的字符出现0次或1次

# 提取qq.
s = "[email protected]"
# re.findall("q.*\.", s)  # 贪婪模式 ['qq.cn.']
re.findall("q.*?\.", s)  # 非贪婪模式

# 匹配abc 和 abccc
s = "abc abcc abccc abcd"
re.findall("abc{1,3}", s)

# 匹配a开头的行  re.S(基于单行匹配) re.M(基于多行匹配)
s = """
this is 
a book which is on the desk.
application
plant trees
"""
re.findall("^a.*", s, re.M)

# 匹配所有行
s = """<div>
轻轻原上草,
一岁一枯荣。
野火烧不尽,
春风吹又生。
</div>
"""
re.findall("<div>.*</div>", s, re.S)
View Code

3、示例:使用正则对糗事百科中数据进行解析并下载

import re
import requests
import os

# 1、指定url
url = "https://www.qiushibaike.com/pic/"
# 2、发起请求 
response = requests.get(url)
# 3、获取页面数据
page_text = response.text
# 4、解析数据
"""
在浏览器中查看源码找到图片对应的标签:
<div class="thumb">
    <a href="/article/121627410" target="_blank">
        <img src="//pic.qiushibaike.com/system/pictures/12162/121627410/medium/D17IKNXRXJLDYTJ3.jpg" alt="image">
    </a>
</div>
通过解析img标签的src属性即可获得图片链接
"""
img_link_list = re.findall('<div class="thumb">.*?<img src="(.*?)".*?>.*?</div>', page_text, re.S)
# 完整的图片链接地址 https://pic.qiushibaike.com/system/pictures/12162/121627410/medium/D17IKNXRXJLDYTJ3.jpg
# 创建一个文件夹用于存放获取的图片
if not os.path.exists("./choushibaike_imgs"):
    os.mkdir("choushibaike_imgs")
    
for url in img_link_list:
    # 给获取的图片链接拼接完整的链接地址
    img_url = "https:" + url
    # 发起请求获取图片二进制数据
    img_data = requests.get(url=img_url).content
    img_name = url.split("/")[-1]  # 或 re.findall("\//.*?medium\/(.*)", url)
    # 5、持久化存储数据
    with open(os.path.join("./choushibaike_imgs", img_name), "wb")as f:
        f.write(img_data)
print("successfully!")
View Code

4、爬取指定范围页码的图片:

import re
import requests
import os

# 1、指定url
url = "https://www.qiushibaike.com/pic/page/"  # https://www.qiushibaike.com/pic/page/3/
# 2、发起请求 
start_page = int(input("输入起始页码:"))
end_page = int(input("输入结束页码:"))
page_text = ""
for i in range(start_page, end_page + 1):
    response = requests.get(url=url + str(i) + "/")
    # 3、获取页面数据
    page_text = page_text + response.text
# 4、解析数据
img_link_list = re.findall('<div class="thumb">.*?<img src="(.*?)".*?>.*?</div>', page_text, re.S)
# 完整的图片链接地址 https://pic.qiushibaike.com/system/pictures/12162/121627410/medium/D17IKNXRXJLDYTJ3.jpg
# 创建一个文件夹用于存放获取的图片
if not os.path.exists("./choushibaike_imgs"):
    os.mkdir("choushibaike_imgs")
    
for url in img_link_list:
    # 给获取的图片链接拼接完整的链接地址
    img_url = "https:" + url
    # 发起请求获取图片二进制数据
    img_data = requests.get(url=img_url).content
    img_name = url.split("/")[-1]  # 或 re.findall("\//.*?medium\/(.*)", url)
    # 5、持久化存储数据
    with open(os.path.join("./choushibaike_imgs", img_name), "wb")as f:
        f.write(img_data)
print("successfully!")
View Code

(二)xpath解析

1、如何使用

(1)下载lxml:pip install lxml

(2)导入包:form lxml import etree

(3)创建etree对象进行指定数据的解析,此时有两种情况:

  • 如果解析的数据是来源于本地:
etree = etree.parse("本地文件路径")
etree.xpath("xpath表达式")
  • 如果数据来源于网络:
etree = etree.HTML("网络请求到的页面数据")
etree.xpath("xpath表达式")

2、常用xpath解析式:

xpath函数返回的结果是一个列表。

# 属性定位:
    找到class属性值为song的div标签
    //div[@class="song"] 
# 层级&索引定位:
    找到class属性值为tang的div的直系子标签ul下的第二个子标签li下的# # 直系子标签a
    //div[@class="tang"]/ul/li[2]/a
# 逻辑运算:
    找到href属性值为空且class属性值为du的a标签
    //a[@href="" and @class="du"]
# 模糊匹配:
    //div[contains(@class, "ng")]
    //div[starts-with(@class, "ta")]
# 取文本:
     /表示获取某个标签下的文本内容
     //表示获取某个标签下的文本内容和所有子标签下的文本内容
    //div[@class="song"]/p[1]/text()
    //div[@class="tang"]//text()
# 取属性:
    //div[@class="tang"]//li[2]/a/@href

3、本地文件解析

建立一个本地测试文件test_file.html:

<html lang="en">
<head>
    <meta charset="UTF-8" />
    <title>本地测试文件</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>
View Code

解析:

from lxml import etree

# 创建etree对象
etr = etree.parse("./test_file.html")

# 找到class属性值为song的div标签
etr.xpath('//div[@class="song"] ')  

# 找到class属性值为tang的div的直系子标签ul下的第二个子标签li下的直系子标签a
etr.xpath('//div[@class="tang"]/ul/li[2]/a')

# 找到href属性值为空且class属性值为du的a标签
etr.xpath('//a[@href="" and @class="du"]')

# 模糊匹配 class属性值包含“ng”的div标签
etr.xpath('//div[contains(@class, "ng")]')

# 模糊匹配 class属性值以“ta”开头的div标签
etr.xpath('//div[starts-with(@class, "ta")]')

# /表示获取某个标签下的文本内容
etr.xpath('//div[@class="song"]/p[1]/text()')

# //表示获取某个标签下的文本内容和所有子标签下的文本内容
etr.xpath('//div[@class="tang"]//text()')

# 获取class属性为tang的div标签下的第二个li标签下的a标签的href属性值
etr.xpath('//div[@class="tang"]//li[2]/a/@href')
View Code

4、xpath插件 xpath.crx

安装xpath插件在浏览器中对xpath表达式进行验证:可以在插件中直接执行xpath表达式

以谷歌浏览器为例:

打开浏览器->更多工具->扩展程序->开启开发者模式->将xpath插件拖到这个页面中

启动和关闭插件的快捷键: ctrl + shift + x

5、示例:对段子网中的段子标题和内容进行解析并存储

import requests
from lxml import etree

url = "https://ishuo.cn/joke"
response = requests.get(url)
page_text = response.text

etr = etree.HTML(page_text)
# 获取页面所有的li标签
li_list = etr.xpath('//div[@id="list"]/ul/li')

# Element类型的对象可以继续调用xpath函数,对该对象表示的局部内容进行指定内容的解析
f = open("duanzi.txt", "w", encoding="utf-8")
for li in li_list:
    title = li.xpath('./div[@class="info"]/a/text()')[0]
    content = li.xpath('./div[@class="content"]/text()')[0]
    f.write(title + ":" + content + "\n\n")
f.close()
print("ok")
View Code

(三)BeautifulSoup解析

BeautifulSoup是Python独有的解析方式,简单、高效。

1、安装

- 需要将pip源设置为国内源,阿里源、豆瓣源、网易源等
   - windows
    (1)打开文件资源管理器(文件夹地址栏中)
    (2)地址栏上面输入 %appdata%3)在这里面新建一个文件夹  pip
    (4)在pip文件夹里面新建一个文件叫做  pip.ini ,内容写如下即可:
        [global]
        timeout = 6000
        index-url = https://mirrors.aliyun.com/pypi/simple/
        trusted-host = mirrors.aliyun.com
   
    - linux
    (1)cd ~2)mkdir ~/.pip
    (3)vi ~/.pip/pip.conf
    (4)编辑内容,和windows一模一样

- 需要安装:
     bs4在使用时候需要一个第三方库,把这个库也安装一下
         pip install lxml
     安装bs4
         pip install bs4

2、使用流程

BeautifulSoup可以将一个html文档转换为一个BeautifulSoup对象,调用该对象中属性和方法进行html文档指定内容的定位查找。

(1)导包:from bs4 import BeautifulSoup

(2)将一个html文档,转化为BeautifulSoup对象,然后通过对象的方法或者属性去查找指定的节点内容,存在两种情况:

  • 转化本地文件:soup = BeautifulSoup(open('本地文件'), 'lxml')
  • 转化网络文件:soup = BeautifulSoup('网络请求到的字符串类型或者字节类型的数据', 'lxml')

常用属性和方法:

    (1)根据标签名查找
        - soup.a   只能找到第一个符合要求的标签
    (2)获取属性
        - soup.a.attrs  获取a所有的属性和属性值,返回一个字典
        - soup.a.attrs['href']   获取href属性
        - soup.a['href']   也可简写为这种形式
    (3)获取内容
        - soup.a.string
        - soup.a.text
        - soup.a.get_text()
       【注意】如果标签还有标签,那么string获取到的结果为None,而其它两个,可以获取文本内容
    (4)find:找到第一个符合要求的标签
        - soup.find('a')  找到第一个符合要求的
        - soup.find('a', title="xxx")
        - soup.find('a', alt="xxx")
        - soup.find('a', class_="xxx")
        - soup.find('a', id="xxx")
    (5)find_all 或者 findAll:找到所有符合要求的标签
        - soup.find_all('a')
        - soup.find_all(['a','b']) 找到所有的a和b标签
        - soup.find_all('a', limit=2)  限制前两个
    (6)根据选择器选择指定的内容
               select:soup.select('#feng')
        - 常见的选择器:标签选择器(a)、类选择器(.)、id选择器(#)、层级选择器
            - 层级选择器:
                div .dudu #lala .meme .xixi  下面好多级
                div > p > a > .lala          只能是下面一级
        【注意】select选择器返回永远是列表,需要通过下标提取指定的对象
View Code
# 对一个本地文档进行解析
from bs4 import BeautifulSoup

f = open('./test_file.html', 'r')
soup = BeautifulSoup(f, 'lxml')

# 查找p标签 只能找到第一个p标签
print(soup.p)

# 获取a标签的属性值,返回一个字典
print(soup.a.attrs)

# 获取内容
print(soup.p.string)
print(soup.p.text)

# find:查找第一个符合要求的标签
print(soup.find('a', class_="du"))

# find_all:找到所有符合要求的标签
print(soup.find_all("p"))
# print(soup.findAll("p"))

# select函数:选择器  返回的是一个列表
print(soup.select("div > img"))
print(soup.select(".tang li"))

f.close()
View Code

3、示例:爬取古诗文网中三国小说里的标题和内容

import requests
from bs4 import BeautifulSoup

url = "http://www.shicimingju.com/book/sanguoyanyi.html"


def get_content(url):
    # 发起请求,获取数据
    page_text = requests.get(url).text
    soup = BeautifulSoup(page_text, "lxml")
    return soup


soup = get_content(url)
a_list = soup.select(".book-mulu > ul > li > a")  # 所有a标签对象

# print(type(a))  # <class 'bs4.element.Tag'>
# Tag类型的对象可以继续调用相应的属性和方法进行数据解析  
f = open("sanguoyanyi.txt", "w", encoding="utf-8")
for a in a_list:
    title = a.text
    # 章节对应的完整地址:http://www.shicimingju.com/book/sanguoyanyi/1.html
    a_link = "http://www.shicimingju.com" + a["href"]
    soup = get_content(a_link)
    contents = soup.find("div", class_ = "chapter_content")
    content = contents.text
    f.write(title + content + "\n\n")
f.close()
print("ok")
View Code

四、selenium和phantomJs处理网页动态加载数据的爬取

有些网站的页面数据是随着鼠标往下滑动才动态的加载出来的,这种情况下,我们可以借助一些工具进行处理。

(一)selenium

1、介绍

selenium是一个第三方库,可以实现让浏览器自动化的操作

2、环境搭建

(1)安装:pip install selenium

(2)获取浏览器的驱动程序

3、使用

# 1、导包
    from selenium import webdriver
# 2、创建一个浏览器对象
    browser = webdriver.Chrome(executable_path='驱动路径')
# 3、使用浏览器发起指定请求
    browser.get(url)

# 使用下面的方法,查找指定的元素进行操作即可
    find_element_by_id            根据id找节点
    find_elements_by_name         根据name找
    find_elements_by_xpath        根据xpath查找
    find_elements_by_tag_name     根据标签名找
    find_elements_by_class_name   根据class名字查找

4、示例:在百度进行指定词条的搜索

from selenium import webdriver
import time

# 创建一个浏览器对象
browser = webdriver.Chrome(executable_path='./chromedriver')

# 使用浏览器发起指定请求
browser.get("https://www.baidu.com")

time.sleep(1)  # 停留1秒(模拟人工操作)
text = browser.find_element_by_id("kw")  # 通过百度输入框的id进行定位
text.send_keys("爬虫")  # 输入检索字段:爬虫

time.sleep(1)
button = browser.find_element_by_id("su")  # 定位提交按钮
button.click()  # 点击按钮

time.sleep(6)
browser.quit()  # 关闭浏览器
View Code

(二)phantomJs

phantomJs是一款无界面的浏览器,使用方法与谷歌驱动差不多,先下载驱动,拷贝到当前项目目录以备用

import time
from selenium import webdriver

browser = webdriver.PhantomJS(executable_path="./phantomjs-2.1.1-macosx/bin/phantomjs")  # 导入驱动路径

# 打开浏览器
browser.get("https://www.baidu.com")

# 截屏保存
browser.save_screenshot("./001打开浏览器.png")

time.sleep(1)
# 定位文本框 输入检索词条
text = browser.find_element_by_id("kw")
text.send_keys("人工智能")
browser.save_screenshot("./002输入词条.png")

time.sleep(1)
# 定位提交按钮
button = browser.find_element_by_id("su")
button.click()
browser.save_screenshot("./003检索结果.png")

# 关闭浏览器
time.sleep(4)
browser.quit()

# 注意:selenium 3.x版本已经不支持PhantomJS,可以下载selenium 2.x版本   pip install selenium==2.48.0
View Code

猜你喜欢

转载自www.cnblogs.com/yanlin-10/p/10549809.html