一、什么是爬虫
二、基础
(一)获取网页源代码
库:urllib
from urllib.request import urlopen
url = 'http://www.baidu.com'
response = urlopen(url)
print(response.read().decode('utf-8'))
(二)网页加载方式
- 静态页面,全部加载;
- 动态网页,数据和页面分开加载和请求。
三、requests 模块
(一)安装
pip install requests
(二)使用
import requests
url = "http://www.baidu.com"
response = requests.get(url)
response.encoding = "utf-8"
print(response.text)
(三)变量访问与伪装
import requests
content = input("请输入要搜索的内容:")
url = f"http://www.baidu.com/s?wd={content}"
headers = {"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36"}
response = requests.get(url, headers=headers)
print(response.text)
使用 headers 自定义请求头部分,修改 UA 以模仿浏览器进行访问。
(四)post 请求获取翻译信息
import requests
url = "https://fanyi.baidu.com/sug"
data = {"kw": input("请输入要翻译的单词:")}
response = requests.post(url, data=data)
print(response.json())
post 在 data 段包含发送的数据,一般返回 json 格式的数据。
(五)get 请求获取信息
import requests
url = "https://movie.douban.com/j/chart/top_list"
data = {"type": 5, "interval_id": "100:90", "action": "", "start": 0, "limit": 20}
headers = {"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/101.0.4951.64 Safari/537.36"}
response = requests.get(url, params=data, headers=headers)
print(response.json())
get 请求时会将 data 段自动拼接到 url 后面。
(六)cookie 处理
import requests
session = requests.Session()
url = "http://www.baidu.com"
data = {"username": "username", "password": "password"}
response = session.post(url, data=data)
print(response.text)
创建一个 Session 对象,可以在同一个 Session 对象中保持 cookie 等信息。除了 session 还可以在 headers 直接写入 cookie 字段。
(七)防盗链
在 headers 中加入 refer,规避溯源反扒机制。
(八)代理使用
import requests
url = "http://www.baidu.com"
proxy = {"http": "http://127.0.0.1:10809", "https": "http://127.0.0.1:10809"}
r = requests.get(url, proxies=proxy)
print(r.text)
使用代理可以减少服务器封 ip 地址的情况,但速度较慢,费用较贵。
四、数据处理
(一)正则表达式
1. 元字符
| 符号 | 含义 |
|---|
| . | 匹配除换行符以外的任意字符 |
| \w | 字母、数字、下划线 |
| \s | 空白符 |
| \d | 数字 |
| \n | 换行符 |
| \t | 制表符 |
| ^ | 字符串开头 |
| $ | 字符串结尾 |
| \W | 非字母、非数字、非下划线 |
| \D | 非数字 |
| \S | 非空白符 |
| a | b |
| () | 分组,匹配括号内表达式 |
| [...] | 匹配字符串中的字符,如 a-z |
| [^...] | 匹配除字符串中字符的所有字符 |
2. 量词
| 符号 | 含义 |
|---|
| * | 重复 0 次或更多次数 |
| + | 重复 1 次或更多次数 |
| ? | 重复 0 或 1 次 |
| {n} | 重复 n 次 |
| {n,} | 重复 n 次或更多次数 |
| {n,m} | 重复 n 到 m 次 |
| .* | 贪婪匹配,尽可能多的匹配 |
| .*? | 惰性匹配,尽可能少的匹配 |
3. 使用:re 库
- 提取所有符合的内容
import re
text = "我的电话号码是 1234567890 和 1234567891,请不要告诉别人。"
pattern = r"\d{10}"
result = re.findall(pattern, text)
print(result)
- 从迭代器获取所有符合的
import re
text = "我的电话号码是 1234567890 和 1234567891,请不要告诉别人。"
pattern = r"\d{10}"
result = re.finditer(pattern, text)
for match in result:
print(match.group())
- 搜索第一个匹配的项
import re
text = "我的电话号码是 1234567890 和 1234567891,请不要告诉别人。"
pattern = r"\d{10}"
result = re.search(pattern, text)
print(result.group())
- 从字符串开头开始匹配 (如果第一个字符不符合则失败)
import re
text = "我的电话号码是 1234567890 和 1234567891,请不要告诉别人。"
pattern = r"\d{10}"
result = re.match(pattern, text)
print(result)
- 预加载
import re
pattern_obj = re.compile(r"\d{10}")
text = "我的电话号码是 1234567890 和 1234567891,请不要告诉别人。"
result1 = pattern_obj.findall(text)
result2 = pattern_obj.finditer(text)
result3 = pattern_obj.search(text)
print(result1)
print(list(result2))
print(result3.group())
- 提取多个内容
<>内写你取得的名称来区别
import re
pattern_obj = re.compile(r'<div class="(?P<class>.*?)">(?P<content>.*?)</div>')
s = """ <div>这是一个标题</div>
<div>这是一个内容</div> """
result = pattern_obj.finditer(s)
for item in result:
class_name = item.group("class")
content = item.group("content")
print(f"类名:{class_name},内容:{content}")
(二)使用 BeautifulSoup 处理
from bs4 import BeautifulSoup
html = """ <div>这是一个标题</div>
<div>这是一个内容</div> """
page = BeautifulSoup(html, "html.parser")
div = page.find("div", class_="title")
print(div.text)
divs = page.find_all("div")
classes = page.find_all("div", class_=True)
print(classes)
for div in divs:
print(div.text)
print(div.get("class"))
find只找一个符合的。
文本用 .text,图片等用 .img 或 .src。
(三)xpath 处理 (lxml)
from lxml import etree
xml = """ <books>
<id>1</id>
<author>
<nickname>Neo</nickname>
<nickname>小王子</nickname>
<nickname>Python 爬虫基础</nickname>
</author>
<price>100</price>
<name>Python 爬虫基础</name>
</books> """
tree = etree.XML(xml)
et = tree.xpath('//nickname/text()')
result = tree.xpath('//nickname')
for item in result:
print(item.text)
one = tree.xpath('//nickname[@class="1"]/text()')[0]
text() 获取节点内容,默认是列表,如果取出第一个元素,可以使用 [0];//表示所有节点,/表示根节点;@class="1" 获取属性为 class 的值为 1 的节点内容。
(四)pyquery
from pyquery import PyQuery as pq
html = """ <div>
<ul>
<li>first item</li>
<li><a href="link2.html">second item</a></li>
<li><a href="link3.html"><span>third item</span></a></li>
<li><a href="link4.html">fourth item</a></li>
<li><a href="link5.html">fifth item</a></li>
</ul>
</div> """
doc = pq(html)
a = doc("li")
b = doc("li a")
c = doc("li a span")
class_names = doc("li").attr("class")
item_text = doc("li").text()
print(class_names)
print(item_text)
print(a)
print(b)
print(c)
attr("class") 获取第一个 li 标签的 class 属性值,多个标签会返回第一个标签的属性值,如果想获取所有标签的属性值,可以使用 items() 方法;text() 获取所有 li 标签的文本内容,多个标签会返回所有标签的文本内容;pyquery 中就是直接用 css 选择器,注意返回的不是字符串,而是 pyquery 对象。
from pyquery import PyQuery as pq
html = """ <div>这是一个标题</div>
<div>这是一个内容</div> """
doc = pq(html)
after = doc(".title").after("<div>这是一个 after 标签</div>")
before = doc(".title").before("<div>这是一个 before 标签</div>")
print(doc)
remove = doc(".title").remove()
remove_attr = doc(".content").remove_attr("class")
print(doc)
add_class = doc(".after").add_class("new-class")
add_attr = doc(".after").attr("data-id", "123")
print(doc)
after()、before() 在.title 标签后面、前面添加一个新的标签;remove() 删除标签;remove_attr("class") 删除标签的 class 属性;add_class("new-class") 给标签添加一个新的 class 属性值;attr("data-id", "123") 给标签添加一个新的属性 data-id,值为 123,如果属性已经存在,会覆盖原有的属性值。
五、提高效率
(一)多线程
from threading import Thread
def task():
for i in range(5):
print(f"线程任务执行第 {i+1} 次")
thread = Thread(target=task)
thread.start()
for i in range(5):
print(f"主线程执行第 {i+1} 次")
通过 Thread 传递参数给函数时使用 args=() 括号内必须为元组,如果只有一个变量,要在后面加逗号。
(二)多进程
from multiprocessing import Process
def task():
for i in range(5):
print(f"子进程任务执行第 {i+1} 次")
pro = Process(target=task)
pro.start()
for i in range(5):
print(f"主进程执行第 {i+1} 次")
(三)线程池
from concurrent.futures import ThreadPoolExecutor
def task(n):
print(f"线程任务执行第 {n} 次")
with ThreadPoolExecutor(max_workers=5) as executor:
for i in range(40):
executor.submit(task, i+1)
print(f"主线程提交了第 {i+1} 个任务")
(四)协程
import asyncio
async def task(n):
for i in range(10):
print(f"异步任务执行第 {n} 次,第 {i+1} 次")
await asyncio.sleep(1)
async def task2(n):
for i in range(10):
print(f"异步任务 2 执行第 {n} 次,第 {i+1} 次")
await asyncio.sleep(1)
async def task3(n):
for i in range(10):
print(f"异步任务 3 执行第 {n} 次,第 {i+1} 次")
await asyncio.sleep(1)
await asyncio.gather(task(1), task2(2), task3(3))
或者:
import asyncio
async def task(n):
for i in range(10):
print(f"异步任务执行第 {n} 次,第 {i+1} 次")
await asyncio.sleep(1)
async def task2(n):
for i in range(10):
print(f"异步任务 2 执行第 {n} 次,第 {i+1} 次")
await asyncio.sleep(1)
async def task3(n):
for i in range(10):
print(f"异步任务 3 执行第 {n} 次,第 {i+1} 次")
await asyncio.sleep(1)
async def main():
tasks = [asyncio.create_task(task(1)), asyncio.create_task(task2(2)), asyncio.create_task(task3(3))]
await asyncio.wait(tasks)
if __name__ == :
asyncio.run(main())
aiohttp
import asyncio
import aiohttp
async def fetch(url):
async with aiohttp.ClientSession() as session:
async with session.get(url) as response:
return await response.text()
async def main():
url = "http://www.baidu.com"
content = await fetch(url)
print(content)
if __name__ == "__main__":
asyncio.run(main())