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())
    
    
提取数据
  1. find_all的使用:

    在提取标签时,第一个参数是标签的名字,若要在提取标签时使用属性进行过滤,那么可以在这个方法中通过关键字参数形式,将属性的名字和对应的值传进去。或使用 attrs属性,将所有的属性及对应值存放在一个字典中。

    提取标签时可以使用 limit参数限制个数。

  2. find 和find_all 的区别:

    find:找到第一个满足条件的标签。 find_all:返回所有满足条件的标签。

  3. 获取标签属性:

    # 1.通过下标操作方式
    href = a['href']
    # 2.通过attrs属性。
    href1 = a.attrs['href']
    
  4. strings、string 和stripped_strings属性以及get_text方法。

    1.string: 获取某个标签下的非标签字符串。如果是多行的话,就获取不到了。

    2.strings: 获取某个标签下的子孙非标签字符串,返回的是一个生成器。

    3.stripped_strings: 获取某个标签下的子孙非标签字符串,会去掉空白字符。

    4.get_text: 获取某个标签下的子孙非标签字符串,不是以列表的形式生成字符串。

  5. 获取标签注释字符串:

    html = '''
    <p><!--我是注释字符串--></p>
    '''
    soup = BeautifulSoup(html,'lxml')
    
    p = soup.find("p")
    print(p.string)
    

    当标签下存在多行字符,那么就不能获取到了。使用contents方法查看有哪些字符串。

  6. 常见四种对象:

    1. Tag: BeautifulSoup中所有的标签都是Tag类型,并且BeaytifulSoup的对象基本上也是一个Tag类型,故一些方法如find、find_all等方法并不是Beautifulsoup的。
    2. NavigableString: 继承自Python中的str,用起来跟Python中的str是一样的
    3. Comment: 继承自BeautifulSoup
  7. contents和children:
    返回某个标签下的直接子元素,其中包括字符串,区别:contents返回的是一个列表,children返回的是一个迭代码。

  8. 示例:

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基本语法
  1. 根据标签的名字选择:

    p{
        background-color: pink;
    }
    
  2. 根据雷类名选择,那么要在类的前面加一个点:

    .line{
        background-color: pink;
    }
    
  3. 根据id名字选择,那么要在id的前面加一个#号:

    #box{
        background-color: pink;
    }
    
  4. 查找子孙元素,那么要在子孙元素中间有资格空格:

    #box p{
        background-color: pink;
    }
    
  5. 查找直接子元素。那么要在父子元素之间加一个>号:

    #box > p{
        background-color: pink;
    }
    
  6. 根据属性的名字进行查找。那么应该先写标签名字,再在中括号中写属性的值。

    input[name='username']{
        background-color: pink;
    }
    
  7. 在根据类名或者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: 将当前线程处于等于状态,并且会释放。可以被其他线程使用 notifynotify_all函数唤醒。被唤醒之后会继续等待上锁,上锁后继续执行下面的代码。

  • notify: 通知某个正在等待的线程,默认是第一个等待的线程。

  • notify_all: 通知所有的正在等待的线程。notifynotify_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。都实现了锁源语。

  1. 初始化 Queue(maxsize):创建一个先进先出的队列。
  2. qsize():返回队列的大小。
  3. empty():判断队列是否为空。
  4. full():判断队列是否满了。
  5. get():从队列中取最后一个数据。
  6. put():将一个数据放入队列。

动态网页数据抓取

爬取AJAX数据的方式

Asynchronous JavaScrip And XML 异步JavaScrip 和XML。

方法一:直接分析ajax调用的接口。然后通过代码请求这个接口。

方法二:使用Selenium+chromedriver模拟浏览器行为获取数据。★

各个浏览器对应的diver下载网站备忘录:

  1. Chrome: https://sites.google.com/a/chromium.org/chromedriver/download
  2. Firefox: https://github.com/mozilla/geckodriver/releases
  3. Edge: https://developer.microsoft.com/en-us/microsoft-edge/tools/webdriver/
  4. 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常用操作
关闭页面:
  1. driver.close():关闭当前页面。
  2. driver.quit():退出整个浏览器。
定位元素:
  1. find_element_by_id:根据id来查找某个元素。
  2. find_element_by_class_name:根据类名查找元素。
  3. find_element_by_name:根据name属性的值来查找元素。
  4. find_element_by_tag_name:根据标签名来查找元素。
  5. find_element_by_xpath:根据xpath语法来获取元素。
  6. find_element_by_css_selector:根据css选择器选择元素。

注意:find_element是获取第一个满足条件的元素,find_elements是获取所有满足条件的元素。

操作表单元素:
  1. 操作输入框:

    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()
    
  2. 操作checkbox:先选中标签,再执行 click事件

    rememberBtn = driver.find_element_by_name('remember')
    rememberBtn.clear()
    
  3. 选择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()
    
  4. 操作按钮:点击,直接调用 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)
页面等待:
  1. 隐式等待:
    调用 driver.implicity_wait。那么在获取不可用的元素之前,会先等待10秒钟时间。

    driver.implicitly_wait(20)
    driver.find_element_by_id('fdsajkfjkad')
    
  2. 显式等待:
    是表明某个条件成立后在执行获取元素的操作。也可以在等待时制定一个最大的时间,若超出这个时间则抛出异常。显式等待应该使用 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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Ouroboroszzs

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值