python 爬虫
python 爬虫
1.开发工具
pycharm:
链接:https://pan.baidu.com/s/1xg8OcKNftLDrcHjKe2ASlw?pwd=41kr
提取码:41kr
python3
2.第一个爬虫的开发
from urllib.request import urlopen url = "http://www.baidu.com" resp = urlopen(url) #print(resp.read().decode("utf-8")) #获取页面源代码 #将页面源代码保存到当前目录下的 mybaidu.html 文件中 with open("mybaidu.html", mode="w", encoding="utf-8") as f: f.write(resp.read().decode("utf-8"))
3.浏览器工具的使用
推荐 Chrome
快捷键:F12
Chrome 各模块使用
Elements 模块:与页面源代码(Ctrl + U)中的代码可能会不一样,该模块做参考
Console 模块:控制台,运行 JavaScript 代码
Sources 模块:整个页面所需要使用到的所有资源,代码、脚本等
Network 模块:整个网页使用的所有资源,请求、响应、图片、js等的加载
XHR:筛选出加载数据的网络请求(主要)
Perserve log:能够保存之前的网络请求(勾选)
Performance 模块:
4.HTTP 协议
HTTP(Hyper Text Transfer Protocol):超文本传输协议
请求:
请求行 -> 请求方式(get/post)请求 URL 地址 协议 请求头 -> 放一些服务器要使用的附加信息 请求体 -> 一般放一些请求参数
响应:
状态行 -> 协议 状态码 响应头 -> 放一些客户端要使用的一些附加信息 响应体 -> 服务器返回的真正客户端要使用的内容(HTML、json)等
请求头中最常见的一些重要内容(爬虫需要):
User-Agent:请求载体的身份标识(用什么发送的请求) Referer:防盗链(该请求是从哪个页面来的,反爬会用到) Cookie:本地字符串数据信息(用户登录信息,反爬的 token)
响应头中的一些重要的内容:
Cookie:本地字符串数据信息(用户登录信息,反爬的 token) 各种神器的莫名其妙的字符串(需要经验判断,一般是 token 字样,防止各种攻击和反爬)
5.requests 模块入门
5.1.模块安装
模块安装:pip install requests
如果安装速度慢的话,可以改用国内源进行下载:
临时使用:pip install -i https://pypi.tuna.tsinghua.edu.cn/simple requests 设为默认: pip install pip -U pip config set global.index-url https://pypi.tuna.tsinghua.edu.cn/simple
5.2.使用
python 脚本的命名不要和模块相同
import requests #爬取百度的页面源代码 url = "http://www.baidu.com" resp = requests.get(url) resp.encoding = "utf-8" #设置编码方式 print(resp.text) #拿到页面源代码
5.3.UA 头反爬
import requests content = input("请输入你要检索的内容:") url = f"https://www.sogou.com/sie?query={content}" headers = { #添加一个请求信息,UA 头 "User-Agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36" } #处理一个小小的反爬 resp = requests.get(url, headers=headers) print(resp.text) #print(resp.request.headers) #可以查看到默认的请求头信息
5.4.GET 请求方法-get,params
get 函数,params 传递参数
import requests url = "https://movie.douban.com/j/chart/top_list" data = { "type": "13", "interval_id": "100:90", "action": "", "start": "0", "limit": "20" } header = {"User-Agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36"} resp = requests.get(url, params=data, headers=header) #print(resp.text) #拿到的是文本字符串 print(resp.request.url) #输出请求的 url print(resp.json()) #直接拿到的是 json 数据
5.5.POST 请求方法
post 函数,data 传递参数
import requests url = "https://movie.douban.com/j/chart/top_list" data = { "kw":input("请输入一个单词:") } resp = requests.post(url, data=data) #print(resp.text) #拿到的是文本字符串 print(resp.json()) #直接拿到的是 json 数据
6.数据解析
四种解析方式
- re 解析(正则)
- bs4 解析
- xpath 解析
- pyquery 解析
四种方式可以混合使用
7.正则表达式
在线测试正则表达式:https://tool.oschina.net/regex/
7.1.常用元字符
元字符:具有固定含义的特殊符号,一个元字符匹配一位
.:匹配除换行符以外的任意字符 \w:匹配字母或数字或下划线 \s:匹配任意的空白符 \d:匹配数字 \n:匹配一个换行符 \t:匹配一个制表符
^:匹配以某字符串开头的,^test
$:匹配以某字符串结尾的,test$
\W:匹配非字母或数字或下划线
\D:匹配非数字
\S:匹配非空白符
a|b:匹配字符a,或字符b
():匹配括号内的表达式,也表示一个组
[...]:匹配字符组种的字符
[^...]:匹配除了字符组中的字符的左右字符
7.2.量词
量词:控制前面的元字符出现的次数
*:重复零次或多次 +:重复一次或多次 ?:重复零次或一次 {n}:重复 n 次 {n,}:重复 n 次或更多次 {n,m}:重复至少 n 次且最多 m 次
7.3.贪婪匹配和惰性匹配
.*:贪婪匹配(尽可能多地匹配) >*?:惰性匹配(尽可能少地匹配)
在爬虫中用得最多的就是惰性匹配
案例
str:玩儿吃鸡游戏,晚上一起打游戏,干嘛呢?打游戏啊
reg:玩儿.*?游戏
匹配结果:玩儿吃鸡游戏
reg:玩儿.*游戏
匹配结果:玩儿吃鸡游戏,晚上一起打游戏,干嘛呢?打游戏
8.re 模块
8.1.基本使用
内置模块
import re #result = re.findall(r"\d+", "我今年18岁,存款280000000块") #print(result) #重点 # result = re.finditer(r"\d+", "我今年18岁,存款280000000块") # for item in result: #从迭代器中拿到内容 # print(item.group()) #从匹配到地结果中拿到数据 #search 只会匹配到第一次匹配地内容 # result = re.search(r"\d+", "我叫张三,今年20岁,我地班级是5年4班") # print(result.group()) #match 在匹配时,是从字符串的开头进行匹配的,类似在正则前面加上了 ^ # result = re.match(r"\d+", "我叫张三,今年20岁,我地班级是5年4班") # print(result) #预加载,提前把正则对象加载完毕 obj = re.compile(r"\d+") #直接使用加载好的正则 result = obj.findall("我叫张三,今年20岁,我地班级是5年4班") print(result)
获取数据
import re #想要提取数据,必须用小括号括起来,可以单独起名字 #(?P<名字>正则) #提取数据的时候,需要 group("名字") s = """ <div class='西游记'><span id='10010'>中国联通</span></div> <div class='西游记'><span id='10086'>中国移动</span></div> """ obj = re.compile(r"<span id='(?P<id>\d+)'>(?P<name>.*?)</span>") result = obj.finditer(s) for item in result: id = item.group("id") print(id) name = item.group("name") print(name)
8.2.案例1-豆瓣top250
先查看想获取的数据是否在页面源代码中
""" 思路: 1.拿到页面源代码 2.编写正则,提取页面数据 3.保存数据 """ import requests import re f = open("top250.csv", mode="w", encoding="utf-8") headers = {"User-Agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36"} for i in range(1, 11): url = f"https://movie.douban.com/top250?start={(i-1)*25}&filter=" resp = requests.get(url, headers=headers) url = "https://movie.douban.com/top250" # resp.encoding = "utf-8" #解决中文乱码问题 pageSource = resp.text # print(pageSource) # 编写正则表达式 # re.S 可以让正则中的 . 匹配换行符 obj = re.compile(r'<div class="item">.*?<span class="title">' r'(?P<name>.*?)</span>.*?<p class="">.*?导演: ' r'(?P<dao>.*?) .*?<br>' r'(?P<year>.*?) .*?<span class="rating_num" property="v:average">' r'(?P<score>.*?)</span>.*?<span>(?P<num>.*?)人评价</span>', re.S) # 进行正则匹配 result = obj.finditer(pageSource) for item in result: name = item.group("name") # 拿结果 dao = item.group("dao") year = item.group("year").strip() # 去除字符串左右两端空白 score = item.group("score") num = item.group("num") f.write(f"{name},{dao},{year},{score}\n") # 可以更换成 csv 模块,进行数据写入 # print(name, dao, year, score, num) f.close() resp.close() print("豆瓣top250提取完毕") #如何翻页提取 #(页数-1)*25 => start
8.3.案例2-获取电影天堂电影名称及对应下载链接
""" 思路: 1.提取到主页面中的每一个电影的背后的那个 url 地址 1.1.拿到 “2024必看热片” 那一块的 HTML 代码 1.2.从刚才拿到的 HTML 代码中提取到 href 值 2.访问子页面,提取到电影的名称以及下载链接 2.1.拿到子页面的页面源代码 2.2.数据提取 """ import requests import re url = "https://www.dy2018.com/" resp = requests.get(url) resp.encoding = "gbk" #print(resp.text) #1.1.提取 2024必看热片 部分的 HTML 代码 obj1 = re.compile(r"2024必看热片.*?<ul>(?P<html>.*?)</ul>", re.S) result1 = obj1.search(resp.text) html = result1.group("html") #1.2.提取a标签中的href值 obj2 = re.compile(r"<li><a href='(?P<href>.*?)' title", re.S) result2 = obj2.finditer(html) obj3 = re.compile(r'<div id="Zoom">.*?◎片 名(?P<moive_name>.*?)<br />.*?' r'<td style="WORD-WRAP: break-word" bgcolor="#fdfddf"><a href="' r'(?P<download>.*?)">', re.S) for item in result2: #拼接出子页面的url child_url = url.strip("/") + item.group("href") child_resp = requests.get(child_url) child_resp.encoding = "gbk" result3 = obj3.search(child_resp.text) movie_name = result3.group("moive_name") download = result3.group("download") print(movie_name, download)
9.CSS 基础语法
9.1.CSS 语法规则
通过 style 属性来编写样式
通过 style 标签,然后使用选择器的形式来编写样式
在 CSS 文件中编写样式,通过 link 引入该文件
9.2.CSS 选择器
id 选择器:#
标签选择器:标签
类选择器:.
选择器分组:,
后代选择器:空格
子选择器:>
相邻选择器:+
属性选择器:[属性=值]
10.bs4 解析
10.1.基本使用
安装:pip install bs4
from bs4 import BeautifulSoup html = """ <ul> <li><a href="zhangwuji.com">张无忌</li> <li id="abc"><a href="zhouxingchi.com">周星驰</li> <li><a href="zhubajie.com">猪八戒</li> <li><a href="wuzetian.com">武则天</li> <a href="jinmaoshiwang.com">金毛狮王</a> </ul> """ #1.初始化 BeautifulSoup 对象 page = BeautifulSoup(html, "html.parser") #page.find("标签名", attrs={"属性":"值"}) #查找某个元素,只会找到一个结果 #page.find_all("标签名", attrs={"属性":"值"}) #找到一堆结果 # li = page.find("li", attrs={"id":"abc"}) # a = li.find("a") # print(a.text) #拿文本 # print(a.get("href")) #拿属性值:.get("属性名") li_list = page.find_all("li") for li in li_list: a = li.find("a") text = a.text href = a.get("href") print(text, href)
10.2.案例1-新发地菜价
import requests from bs4 import BeautifulSoup f = open("新发地菜价.csv", mode="w", encoding="utf-8") url = "http://www.xinfadi.com/marketanalysis/0/list/1.shtml" resp = requests.get(url) #初始化对象 page = BeautifulSoup(resp.text, "html.parser") table = page.find("table", attrs={"class":"hq_table"}) trs = table.find_all("tr")[1:] #拿到除第一行外的所有 tr for tr in trs: tds = tr.find_all("td") name = tds[0].text #品名 low = tds[1].text #最低价 avg = tds[2].text #平均价 hig = tds[3].text #最高价 kind = tds[4].text #规格 dan = tds[5].text #单位 date = tds[6].text #发布日期 #print(name, low, avg, hig, dan, date) f.write(f"{name},{low},{avg},{hig},{dan},{date}") f.close() resp.close() print("爬取成功")
10.3.案例2-图片抓取
import requests from bs4 import BeautifulSoup """ 注意: 子页面的 url 如果开头是 /,直接在前面拼接上域名即可 子页面的 url 不是 / 开头,此时需要找到主页面的 url,去掉最后一个 / 后面的所有内容,和当前获取的 url 进行拼接 """ domain = "https://www.umei.net" url = "https://www.umei.net/bizhitupian/xiaoqingxinbizhi/" resp = requests.get(url) resp.encoding = "utf-8" n = 1 #图片名称 main_page = BeautifulSoup(resp.text, "html.parser") a_list = main_page.find_all("a", attrs={"calss":"TypeBigPics"}) for a in a_list: href = a.get("href") child_url = domain + href child_resp = requests.get(child_url) #请求到子页面 child_resp.encoding = "utf-8" # print(child_resp.text) # break #测试以下,如果乱码需要进行编码设置 #子页面的 bs 对象 child_bs = BeautifulSoup(child_resp.text, "html.parser") div = child_bs.find("div", attrs={"class":"ImageBody"}) img_src = div.find("img").get("src") #拿到图片的下载路径 #print(img_src) #下载图片 img_resp = requests.get(img_src) with open(f"{n}.jpg", mode="wb") as f: #注意,此时写入到文件的是字节,所以必须是 wb f.write(img_resp.content) #把图片信息写入文件 print(f"第{n}张图片下载完毕") n += 1
import requests from bs4 import BeautifulSoup domain = "https://www.umei.net" url = "https://www.umei.net/tags/qingchun/" resp = requests.get(url) #print(resp.text) #测试网页是否正常显示(如:完整拉取、中文是否乱码) n = 1 main_page = BeautifulSoup(resp.text, "html.parser") l_list = main_page.find_all("li", {"class": "i_list list_n2"}) for l in l_list: a_url = l.find("a")["href"] child_url = domain + a_url child_resp = requests.get(child_url) child_resp.encoding = "utf-8" child_bs = BeautifulSoup(child_resp.text, "html.parser") div = child_bs.find("div", {"class": "image_div"}) img_src = div.find("img")["src"] img_resp = requests.get(img_src, verify=False) name = main_page.find("title") #print(name.text) with open(f"{name.text}.jpg", "wb") as f: f.write(img_resp.content) print(f"第{n}张图片下载完毕") n += 1 # break resp.close() print("爬取完毕")
11.xpath 解析
11.1.基础使用
XPath 是一门在 XML 文档中查找信息的语言
<book> <id>1</id> <name>野花遍地香</name> <price>1.23</price> <author> <nick>周大强</nick> <nick>周芷若</nick> </author> </book>
book、id、name、price。。。都被称为节点
id、name、price、author 被称为 book 的子节点
安装模块:pip install lxml
导入模块两种方式:
from lxml import etree 报错可以考虑以下导入方式: from lxml import html etree = html.etree
from lxml import etree xml = """ <book> <id>1</id> <name>野花遍地香</name> <price>1.23</price> <nick>臭豆腐</nick> <author> <nick id="10086">周大强</nick> <nick id="10010">周芷若</nick> <nick class="jay">周杰伦</nick> <nick class="jolin">蔡依林</nick> <div> <nick>惹了</nick> </div> </author> <partner> <nick id="ppc">胖胖陈</nick> <nick id="ppbc">胖胖不陈</nick> </partner> </book> """ #此时练习只能用 XML et = etree.XML(xml) #result = et.xpath('/book') #找根节点的 book #result = et.xpath('/book/name') # result = et.xpath('/book/name/text()')[0] #text():拿文本,[0]表示取出文本内容 # result = et.xpath('/book//nick') # // 表示的所有的子孙后代 # result = et.xpath('//book/*/nick/text()') #* 通配符 # result = et.xpath('//book/*/*/nick/text()') #result = et.xpath('//book/author/nick[@class="jay"]/text()') #[] 表示属性筛选,@属性名=值 result = et.xpath('//book/partner/nick/@id') #最后一个 / 表示拿到 nick 中的 id 的内容,@属性,可以直接拿到属性值 print(result)
from lxml import etree #xpath 处理 html html = """ <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <title>Title</title> </head> <body> <ul> <li><a href="http://www.baidu.com">百度</a></li> <li><a href="http://www.google.com">谷歌</a></li> <li><a href="http://www.sougou.com">搜狗</a></li> </ul> <ol> <li><a href="feiji">飞机</a></li> <li><a href="dapao">大炮</a></li> <li><a href="huoche">火车</a></li> </ol> <div class="job">李嘉诚</div> <div class="common">胡辣汤</div> </body> </html> """ et = etree.HTML(html) # li_list = et.xpath('/html/body/ul/li[2]/a/text()') # print(li_list) li_list = et.xpath('//li') for li in li_list: href = li.xpath('./a/@href') # ./ 表示当前节点 text = li.xpath('./a/text()') print(href, text) # 后续的爬虫工作...
11.2.案例1-猪八戒网站
""" 1.拿到页面源代码 2.从页面源代码中提取需要的数据、价格、名称、公司名称 """ import requests from lxml import etree url = "https://www.zbj.com/fw/?k=saas" resp = requests.get(url) resp.encoding = "utf-8" #print(resp.text) #提取数据 et = etree.HTML(resp.text) divs = et.xpath('//div[@class="search-result-list-service"]/div') for div in divs: #此时的 div 就是一条数据,对应一个商品信息 # 商品价格 price = div.xpath('./div/div[3]/div/span/text()') # 去除价格为空的情况 if not price: continue price = price[0] company = div.xpath('./div/a/div[2]/div[1]/div/text()')[0] name = div.xpath('./div/div[3]/div[2]/a//text()') # // 表示提取 a 的所有文本 name = "".join(name) print(name, price, company)
11.3.小技巧:找 XPath
F12 下的 Elements 模块
选择需要查找的代码
确定 xpath 路径:
divs = et.xpath('//div[@class="search-result-list-service"]/div')
然后按上述操作,寻找到想要爬取的数据的代码位置,如,价格:
在价格的代码上鼠标右键,选择 Copy -> Copy XPath
然后对复制下来的 xpath 进行调整成自己需要的
12.pyquery 解析
12.1.基础使用
pyquery 可以对 HTML 结构进行修改
from pyquery import PyQuery html = """ <HTML> <div class="aaa">哒哒哒</div> <div class="bbb">嘟嘟嘟</div> </HTML> """ p = PyQuery(html) # 在xxx标签后添加 xxx 新标签 #p("div.aaa").after("""<div class="ccc">吼吼吼</div>""") # 在 xxx 标签里面 添加 #p("div.aaa").append("""<span>yyy</span>""") #修改标签属性值及添加属性 # p("div.bbb").attr("class", "aaa") # p("div.bbb").attr("id", '12306') #前提是该标签没有该属性 # p("div.bbb").remove_attr("id") #删除属性 # p("div.bbb").remove() #删除标签 print(p)
12.2.案例-汽车之家
目标网站代码更变,下面代码不适用
""" 1.提取页面源代码 2.解析页面源代码,提取数据 """ import requests from pyquery import PyQuery def get_page_source(url): resp = requests.get(url) resp.encoding = "utf-8" print(resp.text) def parse_page_source(html): doc = PyQuery(html) mt_list = doc(".mt-10").items() #class="mt-10" for mt in mt_list: # 拿到每一个 mt # 判断是否有汽车经销商 if not mt("div > dl:nth-child(3) > dt:contains(购车经销商"): mt("div > dl:nth-child(2)").after(PyQuery(""" html 相关代码""")) # 提取购买的车型 # 想要在已经提取的内容中获取第一个怎么办 eq(0) # nth-child(1) 在 css 进行选择的时候,选取第一个位置的内容 chexing = mt("div > dl:nth-child(1) > dd").eq(0).text().replace("\n", "").replace(" ","") didian = mt("div > dl:nth-child(2) > dd").text() shijian = mt("div > dl:nth-child(4) > dd").text() jiage = mt("div > dl:nth-child(5) > dd").text().replace(" 万元", "") youhao = mt("div > dl:nth-child(6) > dd > p:nth-child(1)").text().replace(" 升/百公里", "") gonglishu = mt("div > dl:nth-child(6) > dd > p:nth-child(2)").text().replace(" 公里", "") other = mt("div > div > dl > dd").text().split() #print(chexing) #存储到文件中 def main(): #入口函数 url = "https://k.autohome.com.cn/146/" #1.提取页面源代码 html = get_page_source(url) #2.解析页面源代码,提取数据 parse_page_source(html) if __name__ == '__main__': main()
13.requests 进阶
13.1.模拟用户登录
# 登录 -> 得到 cookie # 带着 cookie 去请求到书架 url -> 书架上的内容 # 必须把上面两个操作连起来 # 可以使用 session 进行请求 -> 可以任务 session 是一连串的请求。在这个过程中的 cookie 不会丢失 import requests # 会话 session = requests.session() data = {"loginName": "173xxxx36", "password": "xxx08"} # 1.登录 url = "https://passport.17k.com/ck/user/login" resp = session.post(url, data=data) # print(resp.cookie) # cookie # 2.拿书架上的数据 # session 中是有 cookie 的 resp2 = session.get('https://user.17k.com/ck/author2/shelf?page=1&appKey=2406394919') print(resp2.text)
# 直接从登录页面中拿取cookie获取数据 # resp = requests.get("https://user.17k.com/ck/author2/shelf?page=1&appKey=2406394919", headers={"Cookie":"cookie 值"}) # print(resp.text)
13.2.防盗链
# 1.拿到 contId # 2.拿到 videoStatus 返回的json -> srcURL # 3.srcURL 里面的内容进行修整 # 4.下载视频 import requests # 拉取视频的网址 url = "https://www.pearvideo.com/video_1721605" contId = url.split("_")[1] videoStatusUrl = f"https://www.pearvideo.com/videoStatus.jsp?contId={contId}&mrd=0.6952007481227842" headers = { "User-Agent": "Mozilla/5", # 防盗链 "Referer": url } resp = requests.get(videoStatusUrl, headers=headers) dic = resp.json() srcUrl = dic['videoInfo']['videos']['srcUrl'] systemTime = dic['systemTime'] srcUrl = srcUrl.replace(systemTime, f"cont-{contId}") # 下载视频 with open("a.mp4", "wb") as file: file.write(requests.get(srcUrl).content) # https://video.pearvideo.com/mp4/adshort/20210301/cont-1721692-15618910_adpkg-ad_hd.pm4 真实链接 # https://video.pearvideo.com/mp4/adshort/20210301/1614588366486-15618910_adpkg-ad_hd.pm4 拿到的链接
13.3.代理
# 代理,可以使用第三方的机器来代理你的请求 import requests # https://www.kuaidaili.com/free/intr url = "http://www.baidu.com" # 准备代理信息 proxy = { "http": "http://123.233.245.158:9443", "https": "https://123.233.245.158:9443" } resp = requests.get(url, proxies=proxy) resp.encoding = "utf-8" print(resp.text)
接入第三方
import requests def get_ip(): # 有待完善,如果代理 IP 都用完了,怎么办(再请求一编) url = "第三方生成的 API 链接" resp = requests.get(url) ips = resp.json() for ip in ips['data']['proxy_list']: #拿到每一个ip yield ip # 一个一个地返回 IP def spider(): url = "http://www.baidu.com/" while 1: try: proxy_ip = next(gen) # 拿到代理 IP proxy = { "http": "http://" + proxy_ip, "https": "http://" + proxy_ip } resp = requests.get(url, proxies=proxy) resp.encoding = "utf-8" return resp.text except: print("报错了.") if __name__ == '__main__': gen = get_ip() # gen 就是代理 IP 的生成器 for i in range(10): spider()