Python网络爬虫基础
一、网络请求
ProxyHandler处理器(代理):
-
1.使用”
urllib.request.ProxyHandler
",传入一个代理,这个代理是一个字典,字典的key依赖于代理服务器能够接受的类型,一般是“http"或"https",值是"ip:port". -
2.使用上一步创建的"handler",以及"request.build_opener"创建一个"opener"对象。
-
3.使用上一步创建的"opener",调用"open"函数,发起请求。
from urllib import request #没有使用代理的 # url = 'http://httpbin.org/ip' # resp = request.urlopen((url)) # print(resp.read()) #使用代理的 url = 'http://httpbin.org/ip' #1.使用ProxyHandler, 传入代理构建一个handler handler = request.ProxyHandler({'http':'112.244.244.154:9000'}) #2.使用上面创建的handler构建一个opener opener = request.build_opener((handler)) #3.使用opener 去发送一个请求 resp = opener.open(url) print(resp.read())
request库
发送get请求:
- 发送get请求,直接调用"`requests.get即可,想要发送什么类型的请求就调用什么方法。
import requests
response = requests.get("http://www.baidu.com/")
print(type(response.content))
print(response.content.decode('utf-8'))
-
response的一些属性
import request response = request.get("http://www.baidu.com/") #查看完整url地址 print(response.url) #查看响应头部字符编码 print(response.encoding) #查看响应码 print(response.status_code)
-
response.text和response.content的区别
1.response.content: 这个是直接从网络上抓取的数据。没有经过任何解码。所以是byte类型。
2.response.text: 这是str的数据类型,是rquests库将response.content进行解码的字符串。request 会根据自己的判断选择解码方式,有时会导致乱码。所以这时候就应该
"response.content.decode('utf-8')"
进行手动解码。
发送post请求:
-
发送post请求直接调用
request.post
即可。 -
若返回的是json数据,那么可以调用
response.json()
使json字符转换为字典或列表。import requests data = { 'first':'true', 'pn':'i', 'kd':'python' } headers = { 'referer':'https://www.lagou.com/wn/jobs?labelWords=&fromSearch=true&suginput=&kd=python', 'user-agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/94.0.4606.54 Safari/537.36' } response = requests.post('https://www.lagou.com/jobs/companyAjax.json',data=data,headers=headers) print(response.json())
使用代理
-
在请求方法中,传递
proxies
就可以了。import requests proxy = { 'http':'114.98.114.281:3256' } response = requests.get("http://httpbin.org/ip",proxies=proxy) print(response.text)
requests 处理cookie 信息、处理不信任的SSL证书
-
如果想要多次请求中共享
cookie
,那么应该使用session. -
对于不被信任的网站,将verify处理为False 即可
resp = request.get('http://www.12306.cn/mormhweb/',verify=False) print(resp.content,decode('utf-8'))
二、数据提取
XPath语法和lxml模块
XPath语法
-
选取结点:
选取此节点的所有子节点:nodename
若是在最前面,代表从根节点选取,否则选择某节点的下一节点:/
从全局节点中选择节点,随便哪个位置://
选取某个节点的属性:@ -
谓语
/bookstore/book[1]
选取bookstore下的第一个子元素
/bookstore/book[last()]
选取bookstore下的倒数第二个子元素
bookstore/book[position()<3]
选取bookstore下前面两个子元素
//book[@price]
选取拥有price属性的book元素//book[@price=10]
选取所有属性price等于10的book元素 -
通配符
任意匹配节点:*匹配节中的任何属性:@*
lxml库
-
解析HTML字符串:使用
lxml.etree.HTML
进行解析。htmlElement = etree.HTML(text) #text为字符串 print(etree.tostring(htmlElement,encoding='utf-8').decode("utf-8")) #若出现乱码,则需要运用torsting属性将解码改为"utf-8
-
解析html文件:使用
lxml.etree.parse
进行解析。htmlElement = etree.parse("tencent.html") print(etree.tostring(htmlElement, encoding='utf-8').decode('utf-8'))
-
这个函数默认使用的是
XML
解析器,故若碰到一些不规范的HTML代码时就会解析错误,这是就要自己创建解析器。
parser = etree.HTMLParser(encoding=‘utf-8’)
htmlElement = etree.parse(‘lagou.html’,parser=parser)
print(etree.tostring(htmlElement, encoding=‘utf-8’).decode(‘utf-8’))
#### lxml结合xpath使用
- 获取所有tr标签
```python
trs = html.xpath("//tr")
for tr in trs:
print(etree.tostring(tr,encoding='utf-8').decode("utf-8"))
-
获取第二个tr 标签
tr =html.xpath("//tr[2]")[0] print(etree.tostring(tr,encoding='utf-8').decode("utf-8"))
-
获取所有class等于event的tr标签
trs = html.xpath("//tr[@class='even']") for tr in trs: print(etree.tostring(tr,encoding='utf-8').decode("utf-8"))
-
获取所有a标签的href属性
aList = html.xpath('//a/@href') for a in aList: print("http://hr.tencent.com/" + a)
-
获取所有的职位信息(纯文本)
trs = html.xpath("//tr[position()>1]") positions = [] for tr in trs: # 在某个标签下,再执行xpath函数,获取这个标签的子酸元素 # 那么应该在//之前加一个,代表是在当前元素下获取 href = tr.xpath(".//a/@href")[0] fullurl = 'http://hr.tencent.com/' + href title = tr.xpath("./td[1]//text()")[0] category = tr.xpath("./td[2]//text()")[0] nums = tr.xpath("./td[3]//text()")[0] address = tr.xpath("./td[4]//text()")[0] pubtime = tr.xpath("./td[5]//text()")[0] position = { 'url':fullurl, 'title':title, 'category':category, 'nums':nums, 'address':address, 'pubtime':pubtime } positions.append(position) print(positions)
BeautifulSoup4库
和lxml一样,BeautifulSoup也是一个HTML/XML的解析器,主要的功能也是解析和提取HTML/XML数据。
lxml只会局部遍历,而BeautifulSoup是基于HTML DOM(Document Object Model)的。会载入整个文档,解析整个文档,解析整个DOM数,因此时间和内存开销较大。
BeautifulSoup用来解析HTML比较简单,API支持CSS选择器,Python标准库中的HTML解析器,也支持lxml的XML解析器。
简单使用
-
常用解析器:
BeautifulSoup(markup, "html.parser")
Python标准库BeautifulSoup(markup, "lxml")
lxml HTML解析器BeautifulSoup(markup, "xml")
lxml XML解析器BeautifulSoup(markup, "html5lib")
html5lib -
示例
from bs4 import BeautifulSoup html = """ ... """ bs = BeautifulSoup(html,"lxml") #这里使用lxml解析器进行解析 print(bs.prettify())
提取数据
-
find_all的使用:
在提取标签时,第一个参数是标签的名字,若要在提取标签时使用属性进行过滤,那么可以在这个方法中通过关键字参数形式,将属性的名字和对应的值传进去。或使用
attrs
属性,将所有的属性及对应值存放在一个字典中。提取标签时可以使用
limit
参数限制个数。 -
find 和find_all 的区别:
find:找到第一个满足条件的标签。 find_all:返回所有满足条件的标签。
-
获取标签属性:
# 1.通过下标操作方式 href = a['href'] # 2.通过attrs属性。 href1 = a.attrs['href']
-
strings、string 和stripped_strings属性以及get_text方法。
1.string: 获取某个标签下的非标签字符串。如果是多行的话,就获取不到了。
2.strings: 获取某个标签下的子孙非标签字符串,返回的是一个生成器。
3.stripped_strings: 获取某个标签下的子孙非标签字符串,会去掉空白字符。
4.get_text: 获取某个标签下的子孙非标签字符串,不是以列表的形式生成字符串。
-
获取标签注释字符串:
html = ''' <p><!--我是注释字符串--></p> ''' soup = BeautifulSoup(html,'lxml') p = soup.find("p") print(p.string)
当标签下存在多行字符,那么就不能获取到了。使用contents方法查看有哪些字符串。
-
常见四种对象:
- Tag: BeautifulSoup中所有的标签都是Tag类型,并且BeaytifulSoup的对象基本上也是一个Tag类型,故一些方法如find、find_all等方法并不是Beautifulsoup的。
- NavigableString: 继承自Python中的str,用起来跟Python中的str是一样的
- Comment: 继承自BeautifulSoup
-
contents和children:
返回某个标签下的直接子元素,其中包括字符串,区别:contents返回的是一个列表,children返回的是一个迭代码。 -
示例:
from bs4 import BeautifulSoup
html = """..."""
soup = BeautifulSoup(html,"lxml")
# 1.获取所有的tr标签
trs1 = soup.find_all('tr')
# 2.获取第2个tr标签
tr = soup.find_all('tr',limit=2)[1]
# 结果都是以列表的形式呈现的,通过[1]获得第二个tr标签。
# 3.获取所有class等于even的tr标签
trs2 = soup.find_all('tr',class_='even')
# 也可以写为: trs = soup.find_all('tr',attrs={'class':"even"})
# 4.将所有id 等于test, class等于test的a标签提取出来。
aList1 = soup.find_all('a',id='test',class_='test')
# aList = soup.find_all('a',attrs={'id':'test','class':'test'})
# 5.获取所有a标签的href属性
aList2 = soup.find_all('a')
for a in aList2:
# 1.通过下标操作方式
href = a['href']
# 2.通过attrs属性。
href1 = a.attrs['href']
# 6.获取所有的职位信息(纯文本)
trs = soup.find_all('tr')[1:]
movies = []
for tr in trs:
movie = {}
# tds = tr.find_all("td")
# title = tds[0].string # '.string'只获得字符串
# category = tds[1].string
# nums = tds[2].string
# city = tds[3].string
# pubtime = tds[4].string
# movie['title'] = title
# movie['category'] = category
# movie['nums'] = nums
# movie['city'] = city
# movie['pubtime'] = pubtime
# movies.append(movie)
infos = list(tr.stripped_strings)
# bs4中的strings方法获得非标签的字符串,stripped_strings获得非空白字符
movie['title'] = infos[0]
movie['category'] = infos[1]
movie['nums'] = infos[2]
movie['city'] = infos[3]
movie['pubtime'] = infos[4]
movies.append(movie)
print(movies)
select和css选择器
css基本语法
-
根据标签的名字选择:
p{ background-color: pink; }
-
根据雷类名选择,那么要在类的前面加一个点:
.line{ background-color: pink; }
-
根据id名字选择,那么要在id的前面加一个#号:
#box{ background-color: pink; }
-
查找子孙元素,那么要在子孙元素中间有资格空格:
#box p{ background-color: pink; }
-
查找直接子元素。那么要在父子元素之间加一个>号:
#box > p{ background-color: pink; }
-
根据属性的名字进行查找。那么应该先写标签名字,再在中括号中写属性的值。
input[name='username']{ background-color: pink; }
-
在根据类名或者id进行查找的时候,如果还要根据标签名进行过滤,那么可以在类或id的前面加上标签的名字:
div#line{ background-color: pink; } div.line{ background-color: pink; }
css选择器在bs4中使用
在 BeautifulSoup
中,要使用css选择器,那么应该使用 soup.select()
方法。应该传递一个css选择器给select方法。
#encoding: 'utf-8'
from bs4 import BeautifulSoup
html = """..."""
soup = BeautifulSoup(html,'lxml')
# 1.获取所有的tr标签
trs = soup.select("tr")
for tr in trs:
print(tr)
# 2.获取第二个tr标签
tr = soup.select('tr')[1]
# 3. 获取所有class等于even的tr标签
trs = soup.select('tr[class=even]')
# 4. 获取所有a标签的href属性
aList = soup.select('a')
for a in aList:
href = a['href']
# 5.获取所有的职位信息(纯文本)
trs1 = soup.select('tr')
for tr in trs1:
infos = list(tr.stripped_strings)
print(infos)
正则表达式和re模块
按照一定规则,从某个字符串中匹配出想要的数据,这个规则就是正则表达式。
单个字符的匹配规则
import re
# 1.匹配某个字符串,从头开始匹配,不对就报错
text = 'hello'
ret = re.match('he',text)
print(ret.group())
# 2.‘.’匹配任意的字符,但无法匹配换行符\n,开头匹配
text = 'hello'
ret = re.match('.',text)
print(ret.group())
# 3.‘\d’匹配任意的数字(0-9),开头匹配
text = '19'
ret = re.match('\d',text)
print(ret.group())
# 4.‘\D’匹配任意的非数字
text = '+'
ret = re.match('\D',text)
print(ret.group())
# 5.'\s'匹配空白字符串(\n,\t,\r,空格)
text = '\n'
ret = re.match('\s',text)
print(ret.group())
# 6.'\w'匹配:a-z、A-Z、数字和下划线。
text = '_'
ret = re.match('\w',text)
print(ret.group())
# 7.'\W'正好与'\w'相反。
text = '+'
ret = re.match('\W',text)
print(ret.group())
# 8.[]组合的方式,只要满足中括号中的字符,就可以匹配。
text = '0731-8888'
ret = re.match('[\d\-]',text)
print(ret.group())
部分匹配规则,可以由中括号代替:
- \d: [0-9]
- \D: [ ^0-9]
- \w: [0-9a-zA-Z_]
- \W:[ ^0-9a-zA-Z_]
多个字符的匹配规则
# 9. ‘*’可以匹配0或任意多个字符
text = '0731'
ret = re.match('\d*',text)
print(ret.group())
# 10. ‘+’匹配1个或多个字符
text = '0a31'
ret = re.match('\w+',text)
print(ret.group())
# 11. '?'匹配一个或没有
text = '0a2'
ret = re.match('\d?',text)
print(ret.group())
# 12. '{m}'匹配m个字符。
text = 'abcd'
ret = re.match('\w{2}',text)
print(ret.group())
# 13. '{m,n}'匹配m到n个字符(取最多)
text = 'abcd'
ret = re.match('\w{1,4}',text)
print(ret.group())
####### 小案例 ######
# 14.验证手机号码
text = "18778190987"
ret = re.match('1[34578]\d{9}',text)
print(ret.group())
# 15.验证邮箱
text = "Ouroboroszzs@163.com"
ret = re.match('\w+@[a-z0-9]+\.[a-z]+',text)
print(ret.group())
# 16.验证url
text = 'http://baidu.com/'
ret = re.match('(http|https|ftp)://[^\s]+',text)
print(ret.group())
# 17.验证身份证
text = '32130220020827001X'
ret = re.match('\d{17}[\dxX]',text)
print(ret.group())
开始结束和或语法
# 18. '^'(脱字号)表示以...开始
text = "hello"
# ret = re.match('^h',text) match函数会把规则放在第一个位置开始比对,如果不是h就会认为匹配失败
# print(ret.group()) 因此在使用match函数进行判断时,脱字号的作用不大
#使用search()函数
ret = re.search('^h',text)
print(ret.group())
# 19. '$'表示以...结尾
text = "xxx@163.com"
ret = re.match('\w+@163.com$',text)
print(ret.group())
# 20. '|'匹配多个字符串或者表达式
text = "http"
ret = re.match('ftp|http|https',text)
print(ret.group())
# 21. 贪婪模式与非贪婪模式
# 贪婪模式尽量匹配多的字符
text = "0123456"
ret = re.match('\d+',text)
print(ret.group())
# 费贪婪模式匹配满足最小的条件
text = "0123456"
ret = re.match('\d+?',text)
print(ret.group())
转义字符与原生字符串
import re
text = "apple is $299"
ret = re.search("\$\d+",text)
print(ret.group())
# r = raw(原生)
text = r'\n'
print(text)
# 或者使用转义字符"\"
text = '\\n'
print(text)
text = "\\n"
# 原生字符串中,\是用来做转义的,python中也使用\作为转义字符,因此需要双重转义,这时候正则表达式中如果要匹配一个'\',需要4个‘\',
# 或者使用原生字符串r取消python的内置转义,仅仅让正则转义。
ret = re.match("\\\\n",text)
ret = re.match(r"\\n",text)
print(ret.group())
re模块的部分函数
1.分组
import re
# 分组
text = "apple is $99,orange is $10"
ret = re.search('.*(\$\d+).*(\$\d+)',text)
print(ret.group()) # 将整个字符串匹配出来
print(ret.group(1)) # 提取第一个分组(第一括号内的内容)
print(ret.group(2)) # 提取第二个分组(第二括号内的内容)
print(ret.group(1,2))
# 把所有的子分组都拿出来
print(ret.groups())
2.函数
# findall 函数
text = "apple is $99,orange is $10"
ret = re.findall('\$\d+',text)
print(ret)
# sub 函数
text = "apple is $99,orange is $10"
ret = re.sub('\$\d+',"0",text)
print(ret)
# split 函数
text = "hello&world in hao"
ret = re.split('[^a-zA-Z]',text)
print(ret)
# compile 函数
text = "the number is 20.50"
# r = re.compile('\d+\.?\d*')
r = re.compile(r"""
\d+ #小数点前面的数字
\.? #小数点本身
\d* #小数点后面的数字
""",re.VERBOSE)
ret = re.search(r,text)
print(ret.group())
三、数据存储
json文件处理
JSON支持的数据格式:
1.对象(字典)。使用花括号。
2.数组(列表)。使用中括号。
3.整形、浮点型、bool型、null型。
4.字符串类型(字符串必须用双引号)
import json
# 将python对象dump为json对象。
persons = [
{
'username':"张三",
'age':18,
'country':'china'
},
{
'username':"李四",
'age':20,
'country':'USA'
}
]
# json_str = json.dumps(persons)
with open('person.json','w',encoding='utf-8') as fp:
# fp.write(json_str)
json.dump(persons,fp,ensure_ascii=False) # 关闭ascii默认编码
# 将json字符串load为python数据类型。
json_str = '[{"username": "\u5f20\u4e09", "age": 18, "country": "china"}, {"username": "\u674e\u56db", "age": 20, "country": "USA"}]'
persons = json.loads(json_str)
for person in persons:
print(person)
with open('person.json','r',encoding='utf-8') as fp: #直接从文件中进行读取。
persons = json.load(fp)
for person in persons:
print(person)
csv文件
1.纯文本,使用某个字符集,如ASCIL,Unicode,EBCDIC或GB2312等
2.由记录组成(典型的是每行一条记录)
3.每条记录被分隔符分隔为字段(典型的分隔符有逗号、分号或制表符;有时也可以是空格)
4.每条记录都有同样的字段序列。
csv文件的读取
import csv
with open('stock.csv','r') as fp:
reader = csv.reader(fp)
titles = next(reader)
for x in reader:
print(x)
若想要在获取标题时通过标题来获取,那么可使用 DictReader
。
import csv
with open('stock.csv','r') as fp:
reader = csv.DictReader(fp)
for x in reader:
print(x['turnoverVol'])
csv文件的写入
写入数据到csv文件,需要创建一个 writer
对象,主要用到两个方法。一个是 writerow
,这个是写入行。一个是 writerows
这个是写入多行。
import csv
headers = ['name','age','classroom']
values = [
('zhiliao',18,'111'),
('wena',20,'222'),
('bbc',21,'111')
]
with open('test.csv','w',newline='') as fp:
writer = csv.writer(fp)
writer.writerow(headers)
writer.writerows(values)
也可以使用字典的方式把数据写进去。使用 DictWriter
。
import csv
headers = ['name','age','classroom']
values = [
{'name':'wena','age':20,'classroom':'222'},
{'name':'abc','age':23,'classroom':'111'},
]
with open('test.csv','w',newline='') as fp:
writer = csv.DictWriter(fp,headers)
writer = csv.writeheader()
writer.writerow({'name':'zzs','age':20,'classroom':'222'})
writer.writerows(values)
四、爬虫进阶
多线程爬虫
threading模块
import time
# 单线程方式
def coding():
for x in range(3):
print('正在编码%s'%x)
time.sleep(1)
def drawing():
for x in range(3):
print('正在画图%s'%x)
time.sleep(1)
def main():
coding()
drawing()
if __name__ == '__main__':
main()
# 采用多线程方式*********************************************
import threading
def coding():
for x in range(3):
print('正在编码%s'%x)
time.sleep(1)
def drawing():
for x in range(3):
print('正在画图%s'%x)
time.sleep(1)
def main():
t1 = threading.Thread(target=coding)
t2 = threading.Thread(target=drawing)
t1.start()
t2.start()
if __name__ == '__main__':
main()
继承自 threading.Thread
类
import threading
import time
class CodingThread(threading.Thread):
def run(self):
for x in range(3):
print('正在编码%s' % threading.current_thread())
time.sleep(1)
class DrwaingThread(threading.Thread):
def run(self):
for x in range(3):
print('正在画图%s' % threading.current_thread())
time.sleep(1)
def main():
t1 = CodingThread()
t2 = DrwaingThread()
t1.start()
t2.start()
if __name__ == '__main__':
main()
多线程共享全局变量的问题
import threading
VALUE = 0
gLock = threading.Lock()
def add_value():
global VALUE # 声明使用全局变量VALUE
gLock.acquire() # 锁机制:加锁
for x in range(1000000):
VALUE += 1
gLock.release() # 锁机制:解锁
print('value:%d'%VALUE)
def main():
for x in range(2):
t = threading.Thread(target=add_value)
t.start()
if __name__ == '__main__':
main()
生产者模式和消费者模式
Lock版本生产者和消费者模式
import threading
import random
import time
gMoney = 1000
gLock = threading.Lock()
gTotalTimes = 10
gTimes = 0
class Producer(threading.Thread):
def run(self):
global gMoney,gTimes
while True:
money = random.randint(100,1000)
gLock.acquire()
if gTimes >= 10:
gLock.release()
break
gMoney += money
print("%s生产了%d元钱,剩余%d元钱"%(threading.current_thread(),money,gMoney))
gTimes += 1
gLock.release()
time.sleep(0.5)
class Consumer(threading.Thread):
def run(self):
global gMoney,gTimes
while True:
money = random.randint(100,1000)
gLock.acquire()
if gMoney >= money:
gMoney -= money
print("%s消费了%d元钱,剩余%d元钱"%(threading.current_thread(),money,gMoney))
else:
if gTimes >= gTotalTimes:
gLock.release()
break
print('%s准备消费%d元钱,剩余%d元钱,不足!'%(threading.current_thread(),money,gMoney))
gLock.release()
time.sleep(0.5)
def main():
for x in range(5):
t = Producer(name="生产者线程%d"%x)
t.start()
for x in range(3):
t = Consumer(name="消费者线程%d"%x)
t.start()
if __name__ == '__main__':
main()
Condition版本生产者和消费者模式:
-
acquire
: 上锁。 -
release
: 解锁。 -
wait
: 将当前线程处于等于状态,并且会释放。可以被其他线程使用notify
和notify_all
函数唤醒。被唤醒之后会继续等待上锁,上锁后继续执行下面的代码。 -
notify
: 通知某个正在等待的线程,默认是第一个等待的线程。 -
notify_all
: 通知所有的正在等待的线程。notify
和notify_all
不会释放。并且需要在release
之前调用。
import threading
import random
import time
gMoney = 1000
gCondition = threading.Condition()
gTotalTimes = 10
gTimes = 0
class Producer(threading.Thread):
def run(self):
global gMoney,gTimes
while True:
money = random.randint(100,1000)
gCondition.acquire()
if gTimes >= 10:
gCondition.release()
break
gMoney += money
print("%s生产了%d元钱,剩余%d元钱"%(threading.current_thread(),money,gMoney))
gTimes += 1
gCondition.notify_all() # 通知
gCondition.release()
time.sleep(0.5)
class Consumer(threading.Thread):
def run(self):
global gMoney,gTimes
while True:
money = random.randint(100,1000)
gCondition.acquire()
while gMoney < money:
if gTimes >= gTotalTimes:
gCondition.release()
return # 将整个函数退出
print("%s消费%d元钱,剩余%d元钱,不足!" % (threading.current_thread(),money,gMoney))
gCondition.wait() # 等待
gMoney -= money
print("%s消费了%d元钱,剩余%d元钱" % (threading.current_thread(),money,gMoney))
gCondition.release()
time.sleep(0.5)
def main():
for x in range(5):
t = Producer(name="生产者线程%d"%x)
t.start()
for x in range(3):
t = Consumer(name="消费者线程%d"%x)
t.start()
if __name__ == '__main__':
main()
Queue线程安全队列
提供了同步的、线程安全的队列类,包括FIFO(先进先出)队列Queue,LIFO(后入先出)队列LifoQueue。都实现了锁源语。
- 初始化
Queue(maxsize)
:创建一个先进先出的队列。 qsize()
:返回队列的大小。empty()
:判断队列是否为空。full()
:判断队列是否满了。get()
:从队列中取最后一个数据。put()
:将一个数据放入队列。
动态网页数据抓取
爬取AJAX数据的方式
Asynchronous JavaScrip And XML 异步JavaScrip 和XML。
方法一:直接分析ajax调用的接口。然后通过代码请求这个接口。
方法二:使用Selenium+chromedriver模拟浏览器行为获取数据。★
各个浏览器对应的diver下载网站备忘录:
- Chrome: https://sites.google.com/a/chromium.org/chromedriver/download
- Firefox: https://github.com/mozilla/geckodriver/releases
- Edge: https://developer.microsoft.com/en-us/microsoft-edge/tools/webdriver/
- Safari: https://webkit.org/blog/6900/webdriver-support-in-safari-10
from selenium import webdriver
driver_path = r'D:\Download\chromedriver\chromedriver.exe'
driver = webdriver.Chrome(executable_path=driver_path)
driver.get('https://www.baidu.com/')
print(driver.page_source)
selenium常用操作
关闭页面:
driver.close()
:关闭当前页面。driver.quit()
:退出整个浏览器。
定位元素:
find_element_by_id
:根据id来查找某个元素。find_element_by_class_name
:根据类名查找元素。find_element_by_name
:根据name属性的值来查找元素。find_element_by_tag_name
:根据标签名来查找元素。find_element_by_xpath
:根据xpath语法来获取元素。find_element_by_css_selector
:根据css选择器选择元素。
注意:find_element
是获取第一个满足条件的元素,find_elements
是获取所有满足条件的元素。
操作表单元素:
-
操作输入框:
from selenium import webdriver # 常见表单元素:input type=text/password/email/number' # button、input[type ='submit] # checkbox: input='checkbox' # select:下拉列表 driver_path = r'D:\Download\chromedriver\chromedriver.exe' driver = webdriver.Chrome(executable_path=driver_path) driver.get('https://www.baidu.com/') # 第一步:找到这个元素,第二部:使用 `send_keys(value)`,将数据填充进去。 inputTag = driver.find_element_by_id('kw') inputTag.send_keys('python') # 使用 clear方法可以清除输入框中的内容 inputTag.clear()
-
操作checkbox:先选中标签,再执行
click
事件rememberBtn = driver.find_element_by_name('remember') rememberBtn.clear()
-
选择select: select元素不能直接点击。因为点击后还需要选中元素。这时候selenium就专门为selsect标签提供了一个类
selenium.webdriver.support.ui.Select
。将获取到的元素当成参数传到这个类中,创建这个对象。from selenium.webdriver.support.ui import Select selectBtn = Select(driver.find_element_by_name('jumpMenu')) # 根据索引选择 selectBtn.select_by_index(1) # 根据值选择 selectBtn.select_by_value("http://m.95xiu.com/") # 根据可视的文本选择 selectBtn.select_by_visible_text("95秀客户端") # 取消选中所有选项 selectBtn.deselect_all()
-
操作按钮:点击,直接调用
click
函数inputTag = driver.find_element_by_id('su') inputTag.click()
行为链:
有时候在页面中的操作可能要很多步,这时候就需要使用鼠标行为链类 ActionChains 来完成。
from selenium import webdriver
from selenium.webdriver.common.action_chains import ActionChains
driver_path = r'D:\Download\chromedriver\chromedriver.exe'
driver = webdriver.Chrome(executable_path=driver_path)
driver.get('https://www.baidu.com/')
inputTag = driver.find_element_by_id('kw')
submitBtn = driver.find_element_by_id('su')
actions = ActionChains(driver)
actions.move_to_element(inputTag)
actions.send_keys_to_element(inputTag,'python')
actions.move_to_element(submitBtn)
actions.click(submitBtn)
actions.perform()
还有更多的鼠标相关的操作:
- click_and_hold(element): 点击但不松开鼠标
- context_click(element): 右键点击
- double_click(element): 双击
https://selenium-python.readthedocs.io/api.html
Cookie操作:
# 获取所有的cookie:
for cookie in driver.get_cookies():
print(cookie)
# 根据cookie的key获取value:
value = driver.get_cookie(key)
# 删除所有的cookie:
driver.delete_all_cookies()
# 删除某个cookie:
driver.delete_cookie(key)
页面等待:
-
隐式等待:
调用driver.implicity_wait
。那么在获取不可用的元素之前,会先等待10秒钟时间。driver.implicitly_wait(20) driver.find_element_by_id('fdsajkfjkad')
-
显式等待:
是表明某个条件成立后在执行获取元素的操作。也可以在等待时制定一个最大的时间,若超出这个时间则抛出异常。显式等待应该使用selenium.webdriver.support.excepted_conditions
期望的条件和selenium.webdriver.support.ui.WebDriverWait
来配合完成。from selenium import webdriver from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC from selenium.webdriver.common.by import By driver_path = r'D:\Download\chromedriver\chromedriver.exe' driver = webdriver.Chrome(executable_path=driver_path) driver.get('https://www.douban.com/') element = WebDriverWait(driver,10).until( EC.presence_of_all_elements_located((By.ID,'form_email')) ) print(element)
切换页面:
switch_to_window
进行切换,具体切换到哪个页面由 driver.window_handles
中查找。
from selenium import webdriver
driver_path = r'D:\Download\chromedriver\chromedriver.exe'
driver = webdriver.Chrome(executable_path=driver_path)
driver.get('https://www.baidu.com/')
# driver.get('https://douban.com/')
driver.execute_script("window.open('https://www.douban.com/')")
print(driver.current_url) # 此时的driver仍然停留在 baidu.com
driver.switch_to.window(driver.window_handles[1])
# driver.window_handles是一个列表,里面装的都是窗口句柄,会按照打开页面的顺序来存储。
print(driver.current_url) # 此时完成切换
设置代理ip:
以Chrome浏览器为例:
from selenium import webdriver
driver_path = r'D:\Download\chromedriver\chromedriver.exe'
options = webdriver.ChromeOptions()
options.add_argument("--proxy-server=http://220.168.52.245:53548")
driver = webdriver.Chrome(executable_path=driver_path,chrome_options=options)
driver.get("http://httpbin.org/ip")
WebElement类补充:
from selenium import webdriver
from selenium.webdriver.remote.webelement import WebElement
driver_path = r'D:\Download\chromedriver\chromedriver.exe'
driver = webdriver.Chrome(executable_path=driver_path)
driver.get('https://www.baidu.com/')
submitBtn = driver.find_element_by_id('su')
print(type(submitBtn))
print(submitBtn.get_attribute("value"))
driver.save_screenshot('baidu.png') # 保存截图
事实上,Webdriver就是WebElement的继承子类。
五、Scrapy框架架构
安装文档:pip install scrapy
,在windows系统下,提示这个错误 ModuleNotFoundError: No module named 'win32api'
,则使用以下命令 pip install pypiwin32