爬虫常用模块笔记
urllib库
urllib库四大模块:
- urllib.request 请求模块
- urllib.error 异常处理模块
- urllib.parse url解析模块
- urllib.robotparser robots.txt解析模块
urlopen 和响应
import urllib.request
response = urllib.request.urlopen('https://www.python.org') # get请求
print(type(response)) # <class 'http.client.HTTPResponse'>
print(response.status) # 200
print(response.getheaders()) #[('Connection', 'close'), ('Content-Length', '51141'), ('Server', 'nginx'),...]
print(response.getheader('Server')) # nginx
print(response.read().decode('utf-8'))
import urllib.parse
import urllib.request
data = bytes(urllib.parse.urlencode({'word': 'hello'}), encoding='utf8') #post请求
response = urllib.request.urlopen('http://httpbin.org/post', data=data)
print(response.read().decode("utf-8"))
import socket
import urllib.request
import urllib.error
try:
response = urllib.request.urlopen('http://httpbin.org/get', timeout=0.1) # timeout参数
except urllib.error.URLError as e:
print(type(e.reason)) # <class 'socket.timeout'>
if isinstance(e.reason, socket.timeout):
print('TIME OUT')
Request
from urllib import request, parse
url = 'http://httpbin.org/post'
headers = {
'User-Agent': 'Mozilla/4.0 (compatible; MSIE 5.5; Windows NT)',
'Host': 'httpbin.org'
}
dict = {
'name': 'Germey'
}
data = bytes(parse.urlencode(dict), encoding='utf8')
req = request.Request(url=url, data=data, headers=headers, method='POST')
response = request.urlopen(req)
print(response.read().decode('utf-8'))
requests库
import requests # 导入模块
r = requests.get('https://www.baidu.com/') # 返回Response对象 <class 'requests.models.Response'>
Response对象常用属性
r.request.headers # 请求头信息 重要 User-Agent身份标识,Referer本次请求从哪个页面来的(防盗链),Cookie本地字符串数据信息
r.headers # 响应头信息 重要 cookie,各种神奇的莫名其妙的字符串(一般是token字样),防止攻击和翻爬
r.status_code # 响应状态码
r.encoding # 获取或者设置当前的编码
r.apparent_encoding # 从内容分析出的编码方式
r.raise_for_status() # 失败请求(非200响应)抛出异常
r.text # 返回字符串内容,等同r.content.decode(r.encoding) <class 'str'>
r.content # 以字节形式(二进制)返回 。可以把图片写入文件 <class 'bytes'>
r.json() # 若返回的是json字符串,用此可以把返回转换为字典, {'args': {}, 'headers': {'Accept': '*/*',....}} 如果不是json格式,产生异常
爬取网页通用代码框架
def getHtmlText(url):
try:
r = requests.get(url, timeout=30)
r.raise_for_status()
r.encoding = r.apparent_encoding
return r.text
except:
return "产生异常"
r.close() #如果不关闭,多次请求可能报错
get方法
headers = {"User-Agent":"...", 'Cookie':'...',"Referer":"..."} #cookie的字符串从开发者工具中获取
proxies = {"https":"https://218.90.33.83:3128"} #代理,Referer防盗链
params={"wd":"python"}
response = requests.get("http://www.baidu.com", params=params, headers=headers, cookies=cookies,proxies=proxies)
# cookies字典从f12工具中cookie字符串加工而来,先以"; "切割,再遍历用等号切割,切割次数为1,得到键值对
# params 等同于带参数的访问(百度搜索python)"http://www.baidu.com/s?wd=python"
# 对https类网址,加verify=False,去掉安全验证
post方法
data = {'name': 'germey', 'age': '22'}
response = requests.post(url,data=data)
post数据来源(data到抓包工具里面获取)
1.固定值 2.输入值 3.预设值-静态文件(需要提前从静态文件中读取)
4.预设值-发请求(需要对指定地址发请求)
5.在客户端生成的 (分析js,模拟生成数据)
文件上传
import requests
files = {'file': open('favicon.ico', 'rb')}
response = requests.post("http://httpbin.org/post", files=files)
print(response.text)
获取cookie
response = requests.get("https://www.baidu.com")
print(response.cookies) # <RequestsCookieJar[<Cookie BDORZ=27315 for .baidu.com/>]>
for key, value in response.cookies.items():
print(key + '=' + value) # BDORZ=27315
会话保持
session = requests.session() #会话,记得刚才的谈话内容
data = {"name":"..","password",".."}
session.post(url, data=data) #返回的cookie记住
resp = session.get(....) #发送请求时自动发送了cookie
#用session是一种办法,另一种办法是headers里面加cookies值
异常处理
import requests
from requests.exceptions import ReadTimeout, ConnectionError, RequestException
try:
response = requests.get("http://httpbin.org/get", timeout = 0.5)
print(response.status_code)
except ReadTimeout:
print('Timeout')
except ConnectionError:
print('Connection error')
except RequestException:
print('Error')
数据解析
三种比较,re最快,xpath最简单,bs4
re 正则表达式
正则的语法:使用元字符进行排列组合用来匹配字符串
常用元字符:
. 匹配任意字符,除了换行符,当re.DOTALL标记被指定时,则可以匹配包括换行符的任意字符。
\w 匹配字母或数字或下划线,等同[A-Za-z0-9_] \W 等同[^A-Za-z0-9_]
\s 匹配任意的空白符,包括空格、制表符、换页符、换行,等价于 [\t\n\r\f]. \S 非空白符,不包括换行
\d 匹配数字 \D 匹配非数字
\n 匹配一个换行符
\t 匹配一个制表符
^ 匹配字符串的开始
$ 匹配字符串的结束
a|b 匹配字符a或者字符b
() 匹配括号内的表达式,也表示一个组
[...] 用来表示一组字符,单独列出:[amk] 匹配 'a','m'或'k'
[^...] 不在[]中的字符:[^abc] 匹配除了a,b,c之外的字符。
量词(控制前面元字符出现的次数):
*重复零次或更多次
+重复一次或更多次
? 匹配0个或1个由前面的正则表达式定义的片段,非贪婪方式
{n} 重复n次
{n,} 重复n次或更多次
{n,m} 匹配 n 到 m 次由前面的正则表达式定义的片段,贪婪方式
贪婪匹配和惰性匹配
.* 贪婪匹配
.*? 惰性匹配,非贪婪
re模块方法
- findall 查找所有,返回list
re.findall(r"\d+","5点之前,要给我5000万") # 得到['5','5000'],如果里面有分组项,只返回元组组成的列表,每个元组由分组项组成 - search 返回第一个匹配结果,如果没有匹配,返回None
re.search(r"\d+","5点之前,要给我5000万",re.S).group() # 得到'5' - match 只能从字符串的开头进行匹配
content = 'Hello 1234567 World_This is a Regex Demo' # 对正则表达式里面用到的字符,需要转义,比如匹配字符串里面的.需要写成\.,$需要写成\$
result = re.match('^He.*(\d+)\sWorld(.*)Demo$', content, re.S) # .*为贪婪模式,匹配 "llo 123456",如果是.*?,匹配"llo ",无re.S,逐行找,有re.S把多行字符串当作一个字符串
print(result) # <re.Match object; span=(0, 40), match='Hello 1234567 World_This is a Regex Demo'>
print(result.group(0)) # Hello 1234567 World_This is a Regex Demo # group() 等同于 group(0)
print(result.group(1)) # 贪婪模式 7,非贪婪模式 1234567
print(result.group(2)) # _This is a Regex
print(result.span()) # (0, 40)
# 总结:尽量使用泛匹配、能用search就不用match、使用括号得到匹配目标、尽量使用非贪婪模式、有换行符就用re.S
- sub 替换字符串中每一个匹配的子串后返回替换后的字符串。
content = 'Extra stings Hello 1234567 World_This is a Regex Demo Extra stings'
content = re.sub('\d+', 'Replacement', content)
print(content) # Extra stings Hello Replacement World_This is a Regex Demo Extra stings
- 预加载正则表达式
str_ = "我叫孙文平,我今年56岁了。" obj = re.compile(r'我叫(?P<name>.*?),我今年(?P<age>.*?)岁了') # 如果用re.S参数,要加到这里,否则出错 obj.search(str_) # 或者 re.search(obj, str_) # 假如返回对象赋值给变量m,m.group("name"),或者m.group(1),得到“孙文平” # 或者m.groupdict() 得到k:v 字典
BeautifulSoup4库
html = """
<html><head><title>The Dormouse's story</title></head>
<body>
<p class="title" name="dromouse"><b>The Dormouse's story</b></p>
<p class="story">Once upon a time there were three little sisters; and their names were
<a href="http://example.com/elsie" class="sister" id="link1"><!-- Elsie --></a>,
<a href="http://example.com/lacie" class="sister" id="link2">Lacie</a> and
<a href="http://example.com/tillie" class="sister" id="link3">Tillie</a>;
and they lived at the bottom of a well.</p>
<p class="story">...</p>
"""
from bs4 import BeautifulSoup
soup = BeautifulSoup(html, 'lxml') # # 还有'html.parser' , 'xml' ,'html5lib' 解析器
print(soup.title) # <title>The Dormouse's story</title> 选择元素
print(type(soup.title)) # <class 'bs4.element.Tag'>
print(soup.title.name) # title 获取名称
print(soup.p.attrs['name']) # dromouse 获取属性
print(soup.p['name']) # dromouse
print(soup.p.string) # The Dormouse's story 获取内容
print(type(soup.p.string)) # <class 'bs4.element.NavigableString'>
print(soup.find_all('ul')) # 返回两个ul标签组成的列表
print(soup.find_all(attrs={'id': 'list-1'})) # 返回所有id="list-1"的标签的列表
print(soup.find_all(attrs={'name': 'elements'})) # 返回所有name='elements'的标签的列表
print(soup.find_all(id='list-1')) # 返回所有id="list-1"的标签的列表
print(soup.find_all(class_='element')) # 返回所有class='element'的标签的列表,因为class为关键字,故加上下划线
soup.select("a") 通过标签名查找
soup.select(".sister") 通过类名查找,查找class=sister的标签
soup.select("#link1") 通过id查找,查找id="link1"的标签
soup.select('a[href=''.....]') 通过属性查找
soup.select("p #link1 a") 组合查找,p标签id为link1的a标签
# 推荐使用lxml解析库,必要时使用html.parser
# 建议使用find()、find_all() 查询匹配单个结果或者多个结果
Xpath库
安装lxmo, pip install lxml
from lxml import etree
tree = etree.XML(xml) #HTML,parse解析文件
tree.xpath("/html/*/a/text()")[0] #/表示层级关系,第一个/是根节点,*表示这级类型任意,text()取a标签的文本,返回一个列表,一般取第一个[0]
tree.xpath("//a/@href") #//表示查找各个层级的a标签,@href a标签中href的值
tree.xpath("/html/body/ul/li[2]/a/text()")[0] #ul下第二个li标签,text()取文本
tree.xpath("/html/body/ul/li[2]/a/@href")[0] #ul下第二个li标签下的a标签,取其href属性,得到一个列表,取第一个元素
tree.xpath("/html/body/ul/li/a[@href='dabao']/text()") #href='dapao'的a标签
li.xpath("./a/text()") #li是xpath查找到的li节点,在li标签内查找a标签
pyquery库
from pyquery import PyQuery as pq
doc = pq(html) # html 可以是字符串,url(url="..."), 文件名(filename="...")。doc是PyQuesy对象
基本css选择器及查找节点(返回的都是PyQuesy对象,返回的若是多个节点,用.items()转换后遍历)
- css选择器:参见BeautifulSoup库的select方法,返回的也是PyQuesy对象
for item in doc('#container .list li').items(): #id为container下的,class为list下的,li标签。
print(item.text()) #items()转换为 generator object,可遍历对象
- 查找子节点
用find(查找子孙节点)或者children(查找子节点)方法,传入的是css选择器,返回也是PyQuesy对象
PyQuesy对象.find('li') PyQuesy对象.children('li') - 查找父节点
用parent方法获取某个节点的父节点, parents方法获取祖先节点(可以传入css选择器)
PyQuesy对象.parent(), PyQuesy对象.parents('.wrap') - 查找兄弟节点
用siblins获取兄弟节点
PyQuesy对象.siblings('.active')
获取信息 - 获取属性
单个PyQuesy对象.attr(属性名称) a.attr('href') 也可以写成 a.attr.href
多个节点的PyQuesy对象,只返回第一个节点的属性,所以只能用.items()转换后遍历,再获取属性 - 获取文本
a.text() 获取节点的(包括子节点的)文本,多个节点的对象返回所有节点的text文本,中间用空格隔开
a.html() 获取节点的html文本,多个节点的对象只返回第一个
节点操作
- removeclass addclass
- attr(属性名) 是获取属性,attr(属性名,字符串) 将改变属性或者增加属性,同样的还有text,html方法
- remove() 方法 移除 此节点
伪类选择器
- 第一个节点 doc("li:first-child")
- 最后一个节点 doc("li:last-child")
- 第二个节点 doc("li:nth-child(2)")
- 包含某一个文本的节点 doc("li: lontains(second)")
selenium
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.common.keys import Keys
browser = webdriver.Chrome()
try:
browser.get('https://www.baidu.com')
print(browser.page_source)
print(browser.current_url)
print(browser.get_cookies())
input = browser.find_element(By.ID,"kw") # find_element 返回找到的第一个元素
input = browser.find_element(By.CLASS_NAME,"mnav")
input = browser.find_element(By.TAG_NAME,"title")
input = browser.find_element(By.LINK_TEXT,"新闻")
input = browser.find_element(By.PARTIAL_LINK_TEXT,"闻")
input = browser.find_element(By.NAME,"tj_login")
input = browser.find_element(By.XPATH,'//*[@id="u"]/a[3]') # chrom xpath 调试器 控制台 $x('xpath代码')
# /绝对路径 //相对路径 *任意标签 索引从1开始 @属性名 多属性定位 //input[@id="kw" and @class='kw-class']
# 函数 文本 //a[text()="百度"] 文本包含 //a[contains(text(), "百度")] 文本开头 //a[starts-with(text(), "百度"]
# 最后一个, 如//div[last()]
input = browserr.find_element(By.CSS_SELECTOR,".s_ipt") # class用. id用# name='wd' 子级"span>input.s_ipt" 子孙级 空格
input = browser.find_elements(By.NAME,"tj_login") # find_elements 返回列表
print(input[1].text) # 获取文本值,如果隐藏用下面语句,其他input.id,location,tag_name,size等
print(input[1].get_attribute("textContent")) # 获取属性:get_attribute("class")
input.send_keys('Python')
input.send_keys(Keys.ENTER)
input.clear()
button = browser.find_element_by_class_name('btn-search')
button.click()
except TimeoutException: # 超时异常
print('Time Out')
except NoSuchElementException: # 元素找不到异常
print('No Element')
finally:
browser.close()
将动作附加到动作链中串行执行
from selenium import webdriver
from selenium.webdriver import ActionChains
browser = webdriver.Chrome()
url = 'http://www.runoob.com/try/try.php?filename=jqueryui-api-droppable'
browser.get(url)
browser.switch_to.frame('iframeResult')
source = browser.find_element_by_css_selector('#draggable')
target = browser.find_element_by_css_selector('#droppable')
actions = ActionChains(browser)
actions.drag_and_drop(source, target) # 拖拽
actions.perform()
执行javasript
from selenium import webdriver
browser = webdriver.Chrome()
browser.get('https://www.zhihu.com/explore')
browser.execute_script('window.scrollTo(0, document.body.scrollHeight)') #滚动条到最下
browser.execute_script('alert("To Bottom")') # 弹出对话框
Frame
from selenium import webdriver
from selenium.common.exceptions import NoSuchElementException
browser = webdriver.Chrome()
url = 'http://www.runoob.com/try/try.php?filename=jqueryui-api-droppable'
browser.get(url)
browser.switch_to.frame('iframeResult') # 切换到子 Frame中
source = browser.find_element_by_css_selector('#draggable')
print(source)
try:
logo = browser.find_element_by_class_name('logo') # 尝试找父Frame中的元素
except NoSuchElementException:
print('NO LOGO')
browser.switch_to.parent_frame() # 切换回父Frame
logo = browser.find_element_by_class_name('logo')
print(logo)
print(logo.text)
窗口之间的切换(选项卡)
import time
from selenium import webdriver
browser = webdriver.Chrome()
browser.get('https://www.baidu.com')
browser.execute_script('window.open()') # 打开一个新的选项卡,并切换到到新的选项卡
browser.get('https://www.taobao.com') # 新选项卡打开淘宝
time.sleep(1)
browser.switch_to_window(browser.window_handles[0]) # 返回到第一个选项卡,但没有在前显示
browser.get('https://python.org')
iframe切换(切换到子iframe中是无法查找到父frame中的元素的)
iframe = web.find_element_by_xpath('...')
web.switch_to.frame(iframe) # 切换到iframe
web.switch_to.default_content() #切换回原页面(web.switch_to.parent_frame())
前进后退
browser = webdriver.Chrome()
browser.get('https://www.baidu.com/')
browser.get('https://www.taobao.com/') # 在同一个选项卡中顺次打开两个网页
browser.back() # 返回到百度网页
time.sleep(1)
browser.forward() # 返回到淘宝网页
browser.close()
Cookies
web.get_cookies()
web.add_cookies({字典})
web.delete_all_cookies()
等待
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
browser = webdriver.Chrome()
browser.get('https://www.taobao.com/')
# browser.implicitly_wait(10) # 隐式等待
wait = WebDriverWait(web,10) # 显式等待,创建对象,最长等待时间10
input = wait.until(EC.prsence_of_element_located((By.ID,'q'))) # 元素加载完毕
button = wait.until(EC.element_to_be_clickable((By.CSS_SELECTOR,'.btn-search'))) # 元素可点击
'''
title_is 标题是某内容
title_contains 标题包含某内容
presence_of_element_located 元素加载出,传入定位元组,如(By.ID, 'p')
visibility_of_element_located 元素可见,传入定位元组
visibility_of 可见,传入元素对象
presence_of_all_elements_located 所有元素加载出
text_to_be_present_in_element 某个元素文本包含某文字
text_to_be_present_in_element_value 某个元素值包含某文字
frame_to_be_available_and_switch_to_it frame加载并切换
invisibility_of_element_located 元素不可见
element_to_be_clickable 元素可点击
staleness_of 判断一个元素是否仍在DOM,可判断页面是否已经刷新
element_to_be_selected 元素可选择,传元素对象
element_located_to_be_selected 元素可选择,传入定位元组
element_selection_state_to_be 传入元素对象以及状态,相等返回True,否则返回False
element_located_selection_state_to_be 传入定位元组以及状态,相等返回True,否则返回False
alert_is_present 是否出现Alert
'''
无头浏览器(后台运行浏览器)
from selenium.webdriver.chrome.options import Options
opt = Options()
opt.add_argument("--headless") # 无头
opt.add_argument("--disbale-gpu") # 没有图形界面
web = Chrome(options=opt)
线程 进程 协程 线程池
进程是资源单位,每个进程至少要有一个线程,线程是执行单位
线程(两种办法)
from threading import Thread # 多线程
from multiprocessing import Process # 多进程
面向过程
def func(str_):
for i in range(1000):
print(str_, i)
t = Thread(target=func, args=("sun",)) # 参数必须是元组
t.start() # 多线程状态为可以开始工作,具体执行时间由cpu决定
for i in range(1000):
print("main", i)
面向对象
class MyThread(Thread):
def __init__(self, str_):
# super().__init__() # 两种父类初始化方法
Thread.__init__(self)
self.str_ = str_
def run(self): # run名字固定,当线程执行时执行本方法
for i in range(1000):
print(self.str_, i)
t = MyThread("sun")
t.start() # 不能写成t.run(),否则就是单线程
for i in range(1000):
print("main", i)
线程池(进程池)
from concurrent.futures import ThreadPoolExecutor,ProcessPoolExecutor
def func(str_):
for i in range(1000):
print(str_, i)
with ThreadPoolExecutor(10) as t:
for i in range(1000):
t.submit(func,f"sun{i}") # 位置参数,同普通方法
# 等待线程池中的任务全部执行完毕,才继续执行(守护)
print("end")
另一种多进程
import multiprocessing
p = multiprocessing.Pool(3) # 创建有3个进程的进程池
for i in range(10):
p.apply_async(函数名,args=(参数,)) # 非阻塞执行,apply是阻塞执行
p.close() # 关闭Pool,不再接收新的任务
p.join() # 主进程阻塞,等待子进程的退出
协程
协程:当程序遇见阻塞操作(io类,比如input,网络get请求,写文件)的时候,可以选择性的切换到其他任务上(time.sleep是同步,不能切换)
多任务异步操作,当然多个协程都是在一个单线程里面
- 入门
import asyncio
异步执行的多个函数前面加 async 关键字,f1=func1(),得到一个协程对象,而不是执行函数
tasks =[f1, f2, f3],将协程对象放入任务列表中
asyncio.run(asyncio.wait(tasks)) 启动执行
time.sleep()是同步,asyncio.sleep()是异步,而且应写成 wait asyncio.sleep()
- 协程程序框架
import asyncio
import time
async def func1():
print("sun wen ping")
await asyncio.sleep(3)
print("sun wen ping")
async def main():
tasks = [f1(), f2(), f3()] #python3.8版本之后,写成asyncio.create_task(func1())
await asyncio.wait(tasks)
if __name__ == "__main__":
t = time.time()
asyncio.run(main())
print(time.time() - t)
- 爬虫协程框架
import aiohttp
import asyncio
urls = ["...","...","..."]
async def aiodownload(url):
name = url.rsplit('=', 1)[1] + ".jpg"
async with aiohttp.ClientSession() as session: # session 等同于 request
async with session.get(url) as resp:
with open(name, "wb") as f:
f.write(await resp.content.read()) # 读取是异步的,需要await挂起
async def aiomain():
tasks = []
for url in urls:
tasks.append(aiodownload(url)) # 3.8版本后asyncio.create_task(func1())
await asyncio.wait(tasks)
if __name__ == '__main__':
asyncio.run(aiomain())

浙公网安备 33010602011771号