URI:统一资源标识符(Uniform Resource Identifier)
URL:统一资源定位符(Uniform Resource Locator)
URN:统一资源名称(Uniform Resource Name)
HTTP:超文本传输协议(Hypertext Transfer Protocol)
HTTPS:以安全为目标的HTTP通道(Hypertext Transfer Protocol)
函数 | 解释 |
---|---|
GET | 请求中的参数包含在URL里面,数据可以在URL中看到。请求提交的数据最多1024字节。 |
POST | 请求的URL不会包含参数,数据都是通过表单形式传输的,会包含在请求体中。请求提交的数据没有限制。 |
参数 | 解释 |
---|---|
Cookie | 维持当前访问会话 |
Referer | 用于标识请求是从哪个页面发过来的 |
User-Agent | 使服务器识别客户端使用的操作系统及版本、浏览器及版本信息 |
Content-Type | 用来表示具体请求中的媒体信息 |
参数 | 解释 |
---|---|
Server | 包含服务器的信息 |
Content-Type | 指定返回的数据是什么类型 |
Set-Cookie | 设置Cookie |
HTML:超文本标记语言(Hypertext Markup Language),用来描述网页的语言
CSS:层叠样式表(Cascading Style Sheets)
JavaScript:简称JS,是一种脚本语言,实现一种实时、动态、交互的页面功能
HTML节点树也叫HTML DOM树。DOM:文档对象模型(Document Object Model)。
静态页面:由HTML代码编写,文字、图片等内容均通过写好的HTML代码来指定
动态页面:动态解析URL中参数的变化,关联数据库并动态呈现不同的页面内容
Session对象用来存储特定用户Session所需的属性及配置信息。
Session在服务端,也就是网站的服务器,用来保存用户的Session信息。
某些网站为了鉴别用户身份、进行Session跟踪而存储在用户本地终端上的数据。
Cookie在客户端,也可以理解为在浏览器端,有了Cookie,浏览器在下次访问相同网页时就会自动附带上它,并发送给服务器,服务器通过识别Cookie鉴别出是哪个用户在访问,然后判断此用户是否处于登录状态,并返回对应的响应。
会话Cookie就是把Cookie放在浏览器内存里,关闭浏览器之后,Cookie即失效。
持久Cookie把Cookie保存在客户端的硬盘中,下次还可以继续使用,用于长久保持客户的登录状态。
代理实际上就是指代理服务器(Proxy Server),功能是代网络用户取得网络信息。
使用代理隐藏真实的IP,让服务器以为是代理服务器在请求自己。
一个进程中同时执行多个线程。
线程是操作系统进行运算调度的最小单元,是进程中的最小运行单元。
同时运行多个进程。
进程是具有一定独立功能的程序在某个数据集合上的一次运行活动,是系统进行资源分配和调度的一个独立单位。
并发(concurrency)是指多个线程对应的多条指令被快速轮换的执行。
并行(parallel)指同一时刻有多条指令在多个处理器上同时执行。
模块 | 解释 |
---|---|
request | 模拟请求的发送 |
error | 异常处理模块,捕获异常,然后进行重试或其他操作保证程序不会意外终止 |
parse | 工具模块,提供许多URL的处理方法 |
robotparser | 主要用来识别网站的robots.txt文件,判断是否可以爬取 |
"""抓取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')
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'))
验证(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)
获取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
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)
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'))
属性reason:返回错误的原因。
from urllib import request, error
try:
response = request.urlopen('https://cuiqingcai.com/404')
except error.URLError as e:
print(e.reason)
属性 | 含义 |
---|---|
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')
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')
from urllib.parse import urlunparse
data = ['https', 'www.baidu.com', 'index.html', 'user', 'a=6', 'comment']
print(urlunparse(data))
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])
from urllib.parse import urlunsplit
data = ['https', 'www.baidu.com', 'index.html', 'a=6', 'comment']
print(urlunsplit(data))
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'))
from urllib.parse import urlencode
params = {
'name':'germey',
'age':25
}
base_url = 'https://www.baidu.com?'
url = base_url + urlencode(params)
print(url)
from urllib.parse import parse_qs
query = 'name=germey&age=25'
print(parse_qs(query))
from urllib.parse import parse_qsl
print(parse_qsl(query))
from urllib.parse import quote
keyword = '壁纸'
url = 'https://www.baidu.com/s?wd=' + quote(keyword)
print(url)
from urllib.parse import unquote
print(unquote(url))
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/'))
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()))
import requests
import re
r = requests.get('https://ssr1.scrape.center/')
pattern = re.compile('(.*?)' , re.S) # 正则表达式
titles = re.findall(pattern, r.text)
print(titles)
import requests
r = requests.get('https://ssr1.scrape.center/static/img/logo.png')
with open('logo.png', 'wb') as fp:
fp.write(r.content)
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)
import requests
data = {'name':'germey', 'age':25}
r = requests.post('https://www.httpbin.org/post', data=data)
print(r.text)
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) # 请求历史
import requests
files = {'file':open('logo.png', 'rb')}
r = requests.post('https://www.httpbin.org/post', files=files)
print(r.text)
import requests
r = requests.get('https://www.baidu.com')
print(r.cookies)
for key, value in r.cookies.items():
print(key + '=' + value)
利用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)
使用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)
为了防止服务器不能及时响应,应该设置一个超时时间,如果超过这个时间还没有得到响应,就报错。
使用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)
"""身份认证(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)
进行大规模爬取,面对大规模爬取且频发的请求时,网站就可能弹出验证码,或者跳转到登陆认证界面,更甚者可能会封禁客户端的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)
传入要匹配的字符串以及正则表达式,从字符串开始位置检测正则表达式是否和字符串相匹配。返回对象包含两个方法,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())
使用括号()将想提取的字符串括起来,调用group方法传入分组的索引即可获得提取结果。
import re
content = 'Hello 1234567 World_This is a Regex Demo'
result = re.match('^Hello\s(\d+)\sWorld', content)
print(result.group(1))
import re
content = 'Hello 1234567 World_This is a Regex Demo'
# '$' 结尾字符串
result = re.match('^He.*(\d+).*Demo$', content)
print(result.group(1))
import re
content = 'Hello 1234567 World_This is a Regex Demo'
# '?' 英文格式下的问号
result = re.match('^He.*?(\d+).*Demo$', content)
print(result.group(1))
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))
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))
当目标字符串中遇到用作用作正则匹配模式的特殊字符时,在此字符前面加反斜线\转义一下即可。
import re
content = '(百度)www.baidu.com'
result = re.match('\(百度\)www\.baidu\.com', content)
print(result)
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))
import re
content = '54aK54yr5oiR54ix5L2g'
content = re.sub('\d+', '', content)
print(content)
将正则字符串编译成正则表达式对象,以便在后面的匹配中复用。
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)
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('' )
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
def scrape_detail(url): # 爬取详情页
return scrape_page(url)
def parse_detail(html): # 解析详情页
# 封面
cover_pattern = re.compile('class="item.*?' ,re.S)
if re.search(cover_pattern, html):
cover = re.search(cover_pattern, html).group(1).strip()
else:
cover = None
# 名称
name_pattern = re.compile('(.*?)' )
if re.search(name_pattern, html):
name = re.search(name_pattern, html).group(1).strip()
else:
name = None
# 类别
categories_pattern = re.compile('(.*?) .*?',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('.*?(.*?)' ,re.S)
if re.search(drama_pattern,html):
drama = re.search(drama_pattern,html).group(1).strip()
else:
drama = None
# 评分
score_pattern = re.compile('(.*?)' ,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}
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)
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()
表达式 | 描述 |
---|---|
nodename | 选取此节点的所有子节点 |
/ | 从当前节点选取直接子节点 |
// | 从当前节点选取所有子孙节点 |
. | 选取当前节点 |
… | 选取当前节点的父节点(parent::) |
@ | 属性匹配([属性值=值])/属性获取(属性) |
text() | 获取当前节点中的文本 |
contains | 属性多值匹配(contains(@属性,值)) |
多值匹配 | [contains(@属性,值) and @属性=值] |
按序选择 | [index]序号从1开始/last()最后一个/position() |
节点轴选择
表达式 | 描述 |
---|---|
ancestor:: | 祖先节点 |
attribute:: | 所有属性值 |
child:: | 子节点 |
descendant:: | 子孙节点 |
following:: | 后继节点 |
following-sibling:: | 同级节点 |
表达式 | 描述 |
---|---|
name | 获取节点名称 |
attrs | 获取节点属性 |
string | 获取内容 |
contents/children | 直接子节点 |
descendants | 所有子孙节点 |
parent | 直接父节点 |
parents | 所有祖先节点 |
next_sibling | 下一个兄弟节点 |
previous_sibling | 上一个兄弟节点 |
next_siblings | 后面所有兄弟节点 |
previous_siblings | 前面所有兄弟节点 |
方法选择器
表达式 | 描述 |
---|---|
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 | 所有节点 / 第一个节点(前面) |