Python3网络爬虫开发实战

文章目录

第一章 爬虫基础

1 HTTP基本原理

1.1 URI和URL

URI:统一资源标识符(Uniform Resource Identifier)
URL:统一资源定位符(Uniform Resource Locator)
URN:统一资源名称(Uniform Resource Name)

1.2 HTTP和HTTPS

HTTP:超文本传输协议(Hypertext Transfer Protocol)
HTTPS:以安全为目标的HTTP通道(Hypertext Transfer Protocol)

1.3 请求(Requset)

1.3.1 请求方式
函数解释
GET请求中的参数包含在URL里面,数据可以在URL中看到。请求提交的数据最多1024字节。
POST请求的URL不会包含参数,数据都是通过表单形式传输的,会包含在请求体中。请求提交的数据没有限制。
1.3.2 请求的网址
1.3.3 请求头:用来说明服务器要使用的附加信息
参数解释
Cookie维持当前访问会话
Referer用于标识请求是从哪个页面发过来的
User-Agent使服务器识别客户端使用的操作系统及版本、浏览器及版本信息
Content-Type用来表示具体请求中的媒体信息
1.3.4 请求体:一般承载的内容是POST请求中的表单数据,对于GET请求,请求体为空

1.4 响应(Response)

1.4.1 响应状态码:表示服务器的响应状态
1.4.2 响应头:包含服务器对请求的应答信息
参数解释
Server包含服务器的信息
Content-Type指定返回的数据是什么类型
Set-Cookie设置Cookie
1.4.3 响应体:响应的正文数据都存在于响应体中

2 Web网页基础

2.1 网页的组成

HTML:超文本标记语言(Hypertext Markup Language),用来描述网页的语言
CSS:层叠样式表(Cascading Style Sheets)
JavaScript:简称JS,是一种脚本语言,实现一种实时、动态、交互的页面功能

2.2 页面的结构

2.3 HTML节点树

HTML节点树也叫HTML DOM树。DOM:文档对象模型(Document Object Model)。

3 爬虫的基本原理

3.1 爬虫概述:获取网页并提取和保存信息的自动化程序

  • 获取网页的源代码
  • 分析源代码,从中提取我们想要的数据
  • 将提取到的数据保存在某处以便后续使用

3.2 JavaScript渲染的页面

4 Session和Cookie

4.1 静态页面和动态页面

静态页面:由HTML代码编写,文字、图片等内容均通过写好的HTML代码来指定
动态页面:动态解析URL中参数的变化,关联数据库并动态呈现不同的页面内容

4.2 无状态HTTP:HTTP协议对事务处理是没有记忆能力的

4.3 Session

Session对象用来存储特定用户Session所需的属性及配置信息。
Session在服务端,也就是网站的服务器,用来保存用户的Session信息。

4.4 Cookie

某些网站为了鉴别用户身份、进行Session跟踪而存储在用户本地终端上的数据。
Cookie在客户端,也可以理解为在浏览器端,有了Cookie,浏览器在下次访问相同网页时就会自动附带上它,并发送给服务器,服务器通过识别Cookie鉴别出是哪个用户在访问,然后判断此用户是否处于登录状态,并返回对应的响应。

4.4.1 会话Cookie和持久Cookie

会话Cookie就是把Cookie放在浏览器内存里,关闭浏览器之后,Cookie即失效。
持久Cookie把Cookie保存在客户端的硬盘中,下次还可以继续使用,用于长久保持客户的登录状态。

5 代理的基本原理

5.1 基本原理

代理实际上就是指代理服务器(Proxy Server),功能是代网络用户取得网络信息。

5.2 爬虫代理

使用代理隐藏真实的IP,让服务器以为是代理服务器在请求自己。

5.3 常见代理设置

  • 高度匿名代理
  • 付费代理服务
  • ADSL拨号
  • 蜂窝代理

6 多线程和多进程的基本原理

6.1 多线程的含义

一个进程中同时执行多个线程。
线程是操作系统进行运算调度的最小单元,是进程中的最小运行单元。

6.2 多进程的含义

同时运行多个进程。
进程是具有一定独立功能的程序在某个数据集合上的一次运行活动,是系统进行资源分配和调度的一个独立单位。

6.3 并行和并发

并发(concurrency)是指多个线程对应的多条指令被快速轮换的执行。
并行(parallel)指同一时刻有多条指令在多个处理器上同时执行。

第二章 基本库的使用

模块解释
request模拟请求的发送
error异常处理模块,捕获异常,然后进行重试或其他操作保证程序不会意外终止
parse工具模块,提供许多URL的处理方法
robotparser主要用来识别网站的robots.txt文件,判断是否可以爬取

1 urllib的使用

1.1 发送请求(request模块)

1.1.1 urlopen:方法read()得到响应的页面内容,属性status得到响应结果的状态码。url参数(必传)。
"""抓取Python官网网页"""
import urllib.request
response = urllib.request.urlopen('https://www.python.org')
print(response.read().decode('utf-8'))
"""调用相关方法和属性获取相关信息"""
print(response.status) # 响应的状态码
print(response.getheaders()) # 获取多个同名请求头对应的一组value值,因此返回枚举类型数据
print(response.getheader('Server')) # 获取单个请求头name对应的value值

data参数:添加该参数时,需要使用bytes方法将参数转化为字流节编码格式的内容,即bytes类型。若传递此参数,请求方式变为POST请求。

"""data参数,若传递此参数请求方式变为POST请求"""
import urllib.parse
import urllib.request
# urlencode方法将字典参数转换为字符串
data = bytes(urllib.parse.urlencode({'name': 'germey'}), encoding='utf-8')
response = urllib.request.urlopen('https://www.httpbin.org/post', data=data)
print(response.read().decode('utf-8'))

timeout参数:用于设置超时时间,单位为秒。

import socket
import urllib.request
import urllib.error
try:
    response = urllib.request.urlopen('https://www.httpbin.org/get', timeout=0.1)
except urllib.error.URLError as e:
    if isinstance(e.reason, socket.timeout):  #判断异常类型
        print('TIME OUT')
1.1.2 Request

url参数(必传)
data参数:传入bytes类型的数据
headers参数:字典类型,请求中的请求头。可以通过调用请求实例的add_header方法添加。

"""通过add_header方法添加header"""
req = request.Request(url=url, data=data, method='POST')
req.add_header('User-Agent', 'Mozilla/4.0 (compatible;MSIE 5.5;Windows NT)')

method参数:字符串,用来指示请求的方法。

from urllib import request, parse
url = 'https://www.httpbin.org/post'
headers = {
    'User-Agent': 'Mozilla/4.0 (compatible;MSIE 5.5;Windows NT)',
    'Host': 'www.httpbin.org'
}
dict = {'name': 'germey'}
data = bytes(parse.urlencode(dict), encoding='utf-8')
req = request.Request(url=url, data=data, headers=headers, method='POST')
response = request.urlopen(req)
print(response.read().decode('utf-8'))
1.1.3 高级用法

验证(HTTPBasicAuthHandler模块):网站启用了基本身份认真,允许页面浏览器或者其他客户端程序在请求网站时提供用户名和口令形式的身份凭证。

from urllib.request import HTTPPasswordMgrWithDefaultRealm,HTTPBasicAuthHandler,build_opener
from urllib.error import URLError
username = 'admin'
password = 'admin'
url = 'https://ssr3.scrape.center/'
p = HTTPPasswordMgrWithDefaultRealm()
p.add_password(None, url, username, password)
auth_handler = HTTPBasicAuthHandler(p) # 实例化一个HTTPBasicAuthHandler对象
opener = build_opener(auth_handler)
try:
    result = opener.open(url)
    html = result.read().decode('utf-8')
    print(html)
except URLError as e:
    print(e.reason)

代理(ProxyHandler模块)

from urllib.error import URLError
from urllib.request import ProxyHandler, build_opener
proxy_handler = ProxyHandler({
    'http': 'http://127.0.0.1:8080',
    'https': 'https://127.0.0.1:8080'
})
opener = build_opener(proxy_handler)
try:
    response = opener.open('https://www.baidu.com')
    print(response.read.decode('utf-8'))
except URLError as e:
    print(e.reason)
1.1.4 Cookie

获取Cookie:CookieJar对象

import http.cookiejar, urllib.request
cookie = http.cookiejar.CookieJar()
handler = urllib.request.HTTPCookieProcessor(cookie)
opener = urllib.request.build_opener(handler)
response = opener.open('https://www.baidu.com')
for item in cookie:
    print(item.name + "=" + item.value)

保存Cookie

  • Mozilla格式:MozillaCookieJar对象
import urllib.request, http.cookiejar
filename = 'cookie.text'
cookie = http.cookiejar.MozillaCookieJar(filename)
handler = urllib.request.HTTPCookieProcessor(cookie)
opener = urllib.request.build_opener(handler)
response = opener.open('https://www.baidu.com')
cookie.save(ignore_discard=True, ignore_expires=True)
  • LWP格式:LWPCookieJar对象
cookie = http.cookiejar.LWPCookieJar(filename)

读取Cookie(以LWPCookieJar格式为例)

import urllib.request, http.cookiejar
cookie = http.cookiejar.LWPCookieJar()
cookie.load('cookie1.text', ignore_discard=True, ignore_expires=True)
handler = urllib.request.HTTPCookieProcessor(cookie)
opener = urllib.request.build_opener(handler)
response = opener.open('https://www.baidu.com')
print(response.read().decode('utf-8'))

1.2 处理异常

1.2.1 URLError类:继承自OSError类,是error异常模块的基类

属性reason:返回错误的原因。

from urllib import request, error
try:
    response = request.urlopen('https://cuiqingcai.com/404')
except error.URLError as e:
    print(e.reason)
1.2.2 HTTPError类:是URLError的子类专门用来处理HTTP请求错误
属性含义
code返回HTTP状态码
reason用于返回错误的原因
headers返回请求头
from urllib import request, error
try:
    response = request.urlopen('https://cuiqingcai.com/404')
except error.HTTPError as e:
    print(e.reason, e.code, e.headers, sep='\n')

因为URLError是HTTPError的父类,所以可以选择先捕获子类的错误,再捕获父类的错误

from urllib import request, error
try:
    response = request.urlopen('https://cuiqingcai.com/404')
except error.HTTPError as e:
    print(e.reason, e.code, e.headers, sep='\n')
except error.URLError as e:
    print(e.reason)
else:
    print('Request Successfully')

1.3 解析链接

1.3.1 urlparse:实现URL的识别和分段
from urllib.parse import urlparse
result = urlparse('https://www.baidu.com/index.html;user?id=5#comment')
print(result)
参数解释
urlstring待解析的URL
scheme默认的协议,scheme参数只在URL中不含协议信息的时候才生效
allow_fragments是否忽略fragment,若此项设置为False,那么fragment部分就会被忽略,它会被解析为path、params、或者query的一部分,而fragment部分为空。

解析结果是一个元组,既可以通过属性名获取内容,也可以用索引顺序获取。

"""ParseResult实际上是一个元组"""
print(result.scheme, result[0], result.netloc, result[1], sep='\n')
1.3.2 urlunparse:用于构造URL,接受的参数是一个可迭代对象,其长度必须是6。
from urllib.parse import urlunparse
data = ['https', 'www.baidu.com', 'index.html', 'user', 'a=6', 'comment']
print(urlunparse(data))
1.3.3 urlsplit:与urlparse方法类似,但其不再单独解析params这一部分(params会合并到path中),返回5个结果。
from urllib.parse import urlsplit
result = urlsplit('https://www.baidu.com/index.html;user?id=5#comment')
print(result)
"""SplitResult返回的也是一个元组"""
print(result.scheme, result[0])
1.3.4 urlunsplit:与urlunparse方法类似,唯一的区别是这里接受的参数的长度必须为5。
from urllib.parse import urlunsplit
data = ['https', 'www.baidu.com', 'index.html', 'a=6', 'comment']
print(urlunsplit(data))
1.3.5 urljoin:生成链接,提供一个base_url作为该方法的第一个参数,将新链接作为第二个参数,通过分析base_url的scheme、netloc和path这三部分,并对新链接缺失的内容进行补充。
from urllib.parse import urljoin
print(urljoin('https://www.baidu.com', 'FAQ.html'))
print(urljoin('https://www.baidu.com', 'https://cuiqingcai.com/FAQ.html'))
print(urljoin('https://www.baidu.com/about.html', 'https://cuiqingcai.com/FAQ.html?question=2'))
print(urljoin('https://www.baidu.com?wd=abc', 'https://cuiqingcai.com/index.php'))
print(urljoin('www.baidu.com', '?category=2#comment'))
print(urljoin('www.baidu.com#comment', '?category=2'))
1.3.6 urlencode:将字典类型的数据转化为GET的请求参数
from urllib.parse import urlencode
params = {
    'name':'germey',
    'age':25
}
base_url = 'https://www.baidu.com?'
url = base_url + urlencode(params)
print(url)
1.3.7 parse_qs:将一串GET请求参数转回字典
from urllib.parse import parse_qs
query = 'name=germey&age=25'
print(parse_qs(query))
1.3.8 parse_qsl:将一串GET参数转化为由元组组成的列表
from urllib.parse import parse_qsl
print(parse_qsl(query))
1.3.9 quote:将中文字符转化为URL编码格式
from urllib.parse import quote
keyword = '壁纸'
url = 'https://www.baidu.com/s?wd=' + quote(keyword)
print(url)
1.3.10 unquote:进行URL解码
from urllib.parse import unquote
print(unquote(url))

1.4 分析Robots协议

1.4.1 Robots协议:网络爬虫排除协议
1.4.2 robotparser

set_url:用来设置robots.txt文件的链接,如果在创建RobotFileParse对象时传入了链接就不需要此方法来设置。

rp = RobotFileParser('https://www.baidu.com/robots.txt')

read:读取robot.txt文件并进行分析,一定要调用这个方法。
parse:用来解析robots.txt文件,传入的参数是robots.txt文件中的某些行内容,它会按照robots.txt的语法规则来分析这些内容。

from urllib.request import urlopen
from urllib.robotparser import RobotFileParser
rp = RobotFileParser()
rp.parse(urlopen('https://www.baidu.com/robots.txt').read().decode('utf-8').split('\n'))
print(rp.can_fetch('Baiduspider', 'http://www.baidu.com'))
print(rp.can_fetch('Baiduspider', 'http://www.baidu.com/homepage/'))
print(rp.can_fetch('Googlebot', 'http://www.baidu.com/homepage/'))

can_fetch:该方法有两个参数,第一个是User-Agent,第二个是要抓取的URL,表示User-Agent指示的搜索引擎是否可以抓取这个URL。

from urllib.robotparser import RobotFileParser
rp = RobotFileParser()
rp.set_url('https://www.baidu.com/robots.txt')
rp.read()
print(rp.can_fetch('Baiduspider', 'http://www.baidu.com'))
print(rp.can_fetch('Baiduspider', 'http://www.baidu.com/homepage/'))
print(rp.can_fetch('Googlebot', 'http://www.baidu.com/homepage/'))

2 requests的使用

2.1 GET请求

2.1.1 基本实例
import requests
r = requests.get('https://www.httpbin.org/get')
print(r.text)

利用params参数可以直接传递参数

import requests
data = {
    'name':'germey',
    'age':25
}
r = requests.get('https://www.httpbin.org/get', params=data)
print(r.text)

网页的返回类型虽然是str类型,但是它很特殊,是JSON格式的。

import requests
r = requests.get('https://www.httpbin.org/get')
print(type(r.text))
print(r.json())
print(type(r.json()))
2.1.2 抓取网页
import requests
import re
r = requests.get('https://ssr1.scrape.center/')
pattern = re.compile('<h2.*?>(.*?)</h2>', re.S) # 正则表达式
titles = re.findall(pattern, r.text)
print(titles)
2.1.3 抓取二进制数据
import requests
r = requests.get('https://ssr1.scrape.center/static/img/logo.png')
with open('logo.png', 'wb') as fp:
    fp.write(r.content)
2.1.4 添加请求头(headers参数)
import requests
headers = {
    'User-Agent':'Mozilla/4.0 (compatible;MSIE 5.5;Windows NT)'
}
r = requests.get('https://ssr1.scrape.center/', headers=headers)
print(r.text)

2.2 POST请求(data参数)

import requests
data = {'name':'germey', 'age':25}
r = requests.post('https://www.httpbin.org/post', data=data)
print(r.text)

2.3 响应

import requests
r = requests.get('https://ssr1.scrape.center/')
print(type(r.status_code), r.status_code)
print(type(r.headers), r.headers)
print(type(r.cookies), r.cookies)
print(type(r.url), r.url)
print(type(r.history), r.history) # 请求历史

2.4 高级用法

2.4.1 文件上传(files参数)
import requests
files = {'file':open('logo.png', 'rb')}
r = requests.post('https://www.httpbin.org/post', files=files)
print(r.text)
2.4.2 设置Cookie(cookies参数)
  1. 获取Cookie
import requests
r = requests.get('https://www.baidu.com')
print(r.cookies)
for key, value in r.cookies.items():
    print(key + '=' + value)
  1. 维持Cookie
    第一种,将Cookie设置到请求头中,然后发送请求。
    第二种,先构造一个RequsetsCookieJar对象,然后对Cookie进行处理和赋值,传递给cookies参数。
2.4.3 维持Session(Session对象)

利用Session可以做到模拟同一个会话而不用担心Cookie的问题,它通常在模拟登录成功之后,进行下一步操作时用到。
Session在平常用的非常广泛,可以用于模拟在一个浏览器中打开同一站点的不同画面。

"""Session维持小实验"""
import requests
requests.get('https://www.httpbin.org/cookies/set/number/123456789')    # 请求一个测试网站
r = requests.get('https://www.httpbin.org/cookies')    # 获取当前的Cookie信息
print(r.text)    # 不能成功获取设置的Cookie
"""Session维持实例(维持同一个Session)"""
s = requests.Session()    # 创建一个Session对象
s.get('https://www.httpbin.org/cookies/set/number/123456789')
r = s.get('https://www.httpbin.org/cookies')
print(r.text)
2.4.4 SSL证书验证

使用verify参数控制是否验证证书,如果将此参数设置为False,那么在请求时就不会再验证证书是否有效。

"""SSL证书验证小实验"""
import requests
response = requests.get('https://ssr2.scrape.center/')
print(response.status_code)    # 报错
"""SLL证书验证跳过(verify参数)"""
response = requests.get('https://ssr2.scrape.center/', verify=False)
print(response.status_code)    # 不报错,出现警告
"""设置忽略警告的方式屏蔽这个警告"""
import requests
from requests.packages import urllib3
urllib3.disable_warnings()    # 忽略警告
response = requests.get('https://ssr2.scrape.center/', verify=False)
print(response.status_code)
"""通过捕获警告到日志的方式忽略警告"""
import logging
import requests
logging.captureWarnings(True)    # 捕获警告
response = requests.get('https://ssr2.scrape.center/', verify=False)
print(response.status_code)
2.4.5 设置超时

为了防止服务器不能及时响应,应该设置一个超时时间,如果超过这个时间还没有得到响应,就报错。
使用timeout参数,其值时发出请求再到服务器返回响应的时间。

"""超时设置(timeout参数)"""
import requests
r = requests.get('https://www.httpbin.org/get', timeout=1)
print(r.status_code)
"""timeout参数(连接时间, 读取时间)"""
import requests
r = requests.get('https://www.httpbin.org/get', timeout=(5, 30))
print(r.status_code)
2.4.6 身份认证(auth参数)
"""身份认证(auth参数)"""
import requests
from requests.auth import HTTPBasicAuth
r = requests.get('https://ssr3.scrape.center/', auth=HTTPBasicAuth('admin', 'admin'))
print(r.status_code)
"""身份认证简化版"""
import requests
r = requests.get('https://ssr3.scrape.center/', auth=('admin', 'admin'))
print(r.status_code)
2.4.7 设置代理

进行大规模爬取,面对大规模爬取且频发的请求时,网站就可能弹出验证码,或者跳转到登陆认证界面,更甚者可能会封禁客户端的IP,为了防止这种情况发生,我们需要设置代理来解决这个问题,需要使用到proxies参数。

from requests import Request,Session
url = 'https://www.httpbin.org/post'
data = {'name':'germey'}
headers = {
    'User-Agent':'Mozilla/4.0 (compatible;MSIE 5.5;Windows NT)'
}
s = Session()
req = Request('POST', url, data=data, headers=headers)    # 构造一个Request对象
prepped = s.prepare_request(req)    # 转换为一个Prepared Request对象
r = s.send(prepped)    # 发送请求
print(r.text)

3 正则表达式

3.1 match

传入要匹配的字符串以及正则表达式,从字符串开始位置检测正则表达式是否和字符串相匹配。返回对象包含两个方法,group方法输出匹配到的内容,span方法输出匹配的范围。

import re
content = 'Hello 123 4567 World_This is a Regex Demo'
print(len(content))
# '^' 匹配字符串的开头
result = re.match('^Hello\s\d\d\d\s\d{4}\s\w{10}', content)
print(result)
print(result.group())
print(result.span())
3.1.1 匹配目标

使用括号()将想提取的字符串括起来,调用group方法传入分组的索引即可获得提取结果。

import re
content = 'Hello 1234567 World_This is a Regex Demo'
result = re.match('^Hello\s(\d+)\sWorld', content)
print(result.group(1))
3.1.2 贪婪匹配(.*):匹配尽可能多的字符
import re
content = 'Hello 1234567 World_This is a Regex Demo'
# '$' 结尾字符串
result = re.match('^He.*(\d+).*Demo$', content)
print(result.group(1))
3.1.3 非贪婪匹配(.*?):匹配尽可能少的字符
import re
content = 'Hello 1234567 World_This is a Regex Demo'
# '?' 英文格式下的问号
result = re.match('^He.*?(\d+).*Demo$', content)
print(result.group(1))
3.1.4 匹配结果在中间尽量用非贪婪匹配,匹配结果在字符串结尾尽量用贪婪匹配。
import re
content = 'http://weibo.com/comment/kEraCN'
result1 = re.match('http.*?comment/(.*?)', content)
result2 = re.match('http.*?comment/(.*)', content)
print('result1',result1.group(1))
print('result2',result2.group(1))
3.1.5 修饰符:控制匹配的模式

re.I:使匹配对大小写不敏感
re.M:多行匹配,影响^和$
re.S:使匹配内容包括换行符在内的所有字符

import re
content = '''Hello 1234567 World_This
is a Regex Demo'''
result = re.match('^He.*?(\d+).*?Demo$', content, re.S)
print(result.group(1))
3.1.6 转义匹配

当目标字符串中遇到用作用作正则匹配模式的特殊字符时,在此字符前面加反斜线\转义一下即可。

import re
content = '(百度)www.baidu.com'
result = re.match('\(百度\)www\.baidu\.com', content)
print(result)

3.2 search:扫描整个字符串,然后返回第一个匹配成功的结果。

import re
content = 'Extra stings Hello 1234567 World_This is a Regex Demo Extra    stings'
result = re.search('He.*?(\d+).*?Demo', content)
print(result.group(1))

3.3 findall:获取与正则表达式相匹配的所有字符串

3.4 sub:修改文本

import re
content = '54aK54yr5oiR54ix5L2g'
content = re.sub('\d+', '', content)
print(content)

3.5 compile

将正则字符串编译成正则表达式对象,以便在后面的匹配中复用。
compile中可以传入修饰符,在使用search、findall方法时就不必额外传,给正则表达式做了一层封装。

import re
content1 = '2019-12-15 12:00'
content2 = '2019-12-17 12:55'
content3 = '2019-12-22 13:21'
pattern = re.compile('\d{2}:\d{2}')
result1 = re.sub(pattern, '', content1)
result2 = re.sub(pattern, '', content2)
result3 = re.sub(pattern, '', content3)
print(result1, result2,result3)

4 基础爬虫案例实战

4.1 爬取目标

  • 爬取站点每一页的电影列表并爬取每个电影的详情页
  • 用正则提取每一部电影的名称、封面、类别、上映时间、评分、剧情简介等
  • 将数据保存为JSON文本文件
  • 利用多线程实现爬取的加速

4.2 爬取列表页

  • 遍历所有页码,构造10页的URL
  • 从每个索引页提取出电影详情页的URL
import requests
import logging # 输出信息
import re
from urllib.parse import urljoin

logging.basicConfig(level=logging.INFO,format='%(asctime)s - %(levelname)s: %(message)s')
BASE_URL = 'https://ssr1.scrape.center'
TOTAL_PAGE = 10

def scrape_page(url): # 通用的爬取页面的方式
    logging.info('scraping %s...',url)
    try:
        response = requests.get(url)
        if response.status_code == 200: # 爬取成功
            return response.text
        # 爬取失败 输出错误日志信息
        logging.error('get invalid status code %s while scraping %s',response.status_code,url)
    except requests.RequestException: # requests的异常处理
        logging.error('error occurred while scraping %s',url,exc_info=True)

def scrape_index(page): # 列表页的爬取
    index_url = f'{BASE_URL}/page/{page}'
    return scrape_page(index_url)

def parse_index(html): # 解析列表页
    pattern = re.compile('<a.*?href="(.*?)".*?class="name">')
    items = re.findall(pattern, html)
    if not items: # 匹配失败
        return []
    for item in items:
        detail_url = urljoin(BASE_URL,item)
        logging.info('get detail url %s',detail_url)
        yield detail_url

4.3 爬取详情页

def scrape_detail(url): # 爬取详情页
    return scrape_page(url)

def parse_detail(html): # 解析详情页
	# 封面
    cover_pattern = re.compile('class="item.*?<img.*?src="(.*?)".*?class="cover">',re.S)
    if re.search(cover_pattern, html):
    	cover = re.search(cover_pattern, html).group(1).strip() 
	else:
		cover = None
	# 名称
    name_pattern = re.compile('<h2.*?>(.*?)</h2>')
    if re.search(name_pattern, html):
    	name = re.search(name_pattern, html).group(1).strip()
    else:
    	name = None
    # 类别
    categories_pattern = re.compile('<button.*?category.*?<span>(.*?)</span>.*?</button>',re.S)
    if re.findall(categories_pattern,html):
    	categories = re.findall(categories_pattern,html)
    else:
    	categories = []
    # 上映时间
    published_at_pattern = re.compile('(\d{4}-\d{2}-\d{2})\s?上映')
    if re.search(published_at_pattern,html):
    	published_at = re.search(published_at_pattern,html).group(1)
    else:
    	published_at =  None
    # 剧情简介
    drama_pattern = re.compile('<div.*?drama.*?>.*?<p.*?>(.*?)</p>',re.S)
    if re.search(drama_pattern,html):
    	drama = re.search(drama_pattern,html).group(1).strip() 
    else:
    	drama =  None
    # 评分
    score_pattern = re.compile('<p.*?score.*?>(.*?)</p>',re.S)
    if re.search(score_pattern,html):
		score = float(re.search(score_pattern,html).group(1).strip()) 
	else:
		score = None  
    return {'cover':cover, 'name':name, 'categories':categories,
        'published_at':published_at, 'drama':drama, 'score': score}

4.4 保存数据

import json
from os import makedirs
from os.path import exists

RESULTS_DIR = 'results' # 保存文件夹
exists(RESULTS_DIR) or makedirs(RESULTS_DIR) # 不存在就创建

def save_data(data): # 保存数据
    name = data.get('name')[:4] # 有非法字符
    data_path = f'{RESULTS_DIR}/{name}.json' # 文件路径
    # ensure_ascii 确保中文字符正常呈现 indent 缩进
    json.dump(data,open(data_path,'w',encoding='utf-8'),ensure_ascii=False,indent=2)

4.5 多进程加速

import multiprocessing

def main(page):
    index_html = scrape_index(page)
    detail_urls = parse_index(index_html)
    # logging.info('detail urls %s', list(detail_urls))
    for detail_url in detail_urls:
        detail_html = scrape_detail(detail_url)
        data = parse_detail(detail_html)
        logging.info('get detail data %s', data)
        logging.info('saving data to json file')
        save_data(data)
        logging.info('data saved successfully')

if __name__ == '__main__':
    pool = multiprocessing.Pool()
    pages = range(1, TOTAL_PAGE + 1)
    pool.map(main, pages)
    pool.close()
    pool.join()

第三章 网页数据的解析提取

1 XPath的使用

表达式描述
nodename选取此节点的所有子节点
/从当前节点选取直接子节点
//从当前节点选取所有子孙节点
.选取当前节点
选取当前节点的父节点(parent::)
@属性匹配([属性值=值])/属性获取(属性)
text()获取当前节点中的文本
contains属性多值匹配(contains(@属性,值))
多值匹配[contains(@属性,值) and @属性=值]
按序选择[index]序号从1开始/last()最后一个/position()

节点轴选择

表达式描述
ancestor::祖先节点
attribute::所有属性值
child::子节点
descendant::子孙节点
following::后继节点
following-sibling::同级节点

2 Beautiful Soup的使用

表达式描述
name获取节点名称
attrs获取节点属性
string获取内容
contents/children直接子节点
descendants所有子孙节点
parent直接父节点
parents所有祖先节点
next_sibling下一个兄弟节点
previous_sibling上一个兄弟节点
next_siblings后面所有兄弟节点
previous_siblings前面所有兄弟节点

方法选择器

  1. find_all(name, attrs,text):查询所有符合条件的元素
    name:查询节点名称
    attrs:查询属性(字典)
    text:用来匹配的文本
  2. find():返回第一个匹配的元素
表达式描述
find_parents / find_parent祖先节点 / 父节点
find_next_siblings / find_next_sibling所有兄弟节点 / 第一个兄弟节点(后面)
find_previous_siblings / find_previous_sibling所有兄弟节点 / 第一个兄弟节点(前面)
find_all_next / find_next所有节点 / 第一个节点(后面)
find_all_previous / find_previous所有节点 / 第一个节点(前面)

第四章 数据的存储

  • 10
    点赞
  • 34
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

胆怯与勇敢

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值