<address id="ttjl9"></address>

      <noframes id="ttjl9"><address id="ttjl9"><nobr id="ttjl9"></nobr></address>
      <form id="ttjl9"></form>
        <em id="ttjl9"><span id="ttjl9"></span></em>
        <address id="ttjl9"></address>

          <noframes id="ttjl9"><form id="ttjl9"></form>

          首頁

          關于Python+selenium 定位瀏覽器彈窗元素

          seo達人

          如果您想訂閱本博客內容,每天自動發到您的郵箱中, 請點這里

          我也是剛學自動化,在自學過程中發現瀏覽器中有一些彈窗元素,無法定位。經過自己的摸索,有一些心得,寫下來供日后自己回顧。
          首先要確定彈窗的類型:
          (1)div彈窗
          (2)新標簽頁彈窗
          (3)alert彈窗 
              
          • 1
          • 2
          • 3
          • 4
          • 5

          一,div彈窗 
          div彈窗是瀏覽器中比較好定位的彈窗,定位的方法與普通的元素一樣。不過這里會有一個坑,明明可以找到這個按鈕,但是就是定位不到。這個就是因為當前有div彈窗彈出的時候,需要設置一下等待時間,等頁面元素加載完畢,再去做其他操作。 
          這里用百度登陸為例子:

          from selenium import webdriver import time def login_baidu(url,username,password): driver.get(url)
              driver.find_element_by_xpath('//*[@id="u1"]/a[7]').click()
              time.sleep(2)
              driver.find_element_by_xpath('//*[@id="TANGRAM__PSP_10__footerULoginBtn"]').click()
              time.sleep(2) # 彈窗出現后,使頁面等待2S login_username = driver.find_element_by_xpath('//*[@id="TANGRAM__PSP_10__userName"]')
              login_username.click()
              login_username.send_keys(username)
          
              login_passwork = driver.find_element_by_xpath('//*[@id="TANGRAM__PSP_10__password"]')
              login_passwork.click()
              login_passwork.send_keys(password)
          
              driver.find_element_by_xpath('//*[@id="TANGRAM__PSP_10__submit"]').click() # 登陸按鈕 if __name__ == "__main__":
              driver = webdriver.Firefox()
              username = r'xxx@qq.com' password = r'xxxx' url = r'https://www.baidu.com' login_baidu(url,username,password) 
              
          • 1
          • 2
          • 3
          • 4
          • 5
          • 6
          • 7
          • 8
          • 9
          • 10
          • 11
          • 12
          • 13
          • 14
          • 15
          • 16
          • 17
          • 18
          • 19
          • 20
          • 21
          • 22
          • 23
          • 24
          • 25
          • 26
          • 27

          二,新標簽頁彈窗 
          新標簽頁彈窗,則需要進行窗口的切換。此處第一個窗口打開百度首頁,在打開一個新窗口打開京東首頁,在兩個窗口之間進行切換。切換到不同的窗口之后,就可以用常規的方法進行元素的定位。

          from selenium import webdriver import time def open_window1(): driver.get("https://www.baidu.com")
             time.sleep(2) def open_window2(): # 用JS的方法打開新窗口,模擬新標簽頁彈窗 js = "window.open('https://www.jd.com')" driver.execute_script(js)
              time.sleep(2) def Switch_Window(): handles = driver.window_handles
              print("打印當前已打開的窗口:"+str(handles)) while(5): # 在兩個窗口之間做五次切換動作 driver.switch_to.window(handles[0])
                  time.sleep(5)
                  driver.switch_to.window(handles[1])
                  time.sleep(5) if __name__ == "__main__" :
              driver = webdriver.Firefox()
              open_window1()
              open_window2()
              Switch_Window() 
              
          • 1
          • 2
          • 3
          • 4
          • 5
          • 6
          • 7
          • 8
          • 9
          • 10
          • 11
          • 12
          • 13
          • 14
          • 15
          • 16
          • 17
          • 18
          • 19
          • 20
          • 21
          • 22
          • 23
          • 24
          • 25
          • 26
          • 27
          handles = driver.window_handles # 獲取當前打開的所有窗口的句柄
          driver.switch_to.window(handles[N]) # 切換到其中一個窗口
          其中,獲取的句柄下標從0開始,即第一個窗口為[0]、第二個窗口為[1],如此類推。使用switch_to.window方法切換到新標簽頁后就可以做其他操作了。 
              
          • 1
          • 2
          • 3

          三、alert彈窗 
          該類型的彈窗暫沒有合適的項目進行練習,待后續完善

          藍藍設計www.syprn.cn )是一家專注而深入的界面設計公司,為期望卓越的國內外企業提供卓越的UI界面設計、BS界面設計 、 cs界面設計 、 ipad界面設計 、 包裝設計 、 圖標定制 、 用戶體驗 、交互設計、 網站建設 、平面設計服務

          CSS學習-選擇器

          seo達人

          如果您想訂閱本博客內容,每天自動發到您的郵箱中, 請點這里

          選擇器

          學習目的 熟練使用 css 選擇器


          css1 中的選擇器


          E #myid         id選擇器

          E .warning      類選擇器

          E F             包含選擇器

          E:link          定義超鏈接未被訪問

          E:visited       定義超鏈接已經被訪問

          E:actice        匹配被激活的元素

          E:hover     鼠標經過的元素

          E:focus     獲取焦點

          E::first-line   元素第一行

          E::first-letter 元素第一個字符


          css2


          *               通配選擇文檔中所有元素

          E[foo]          包含foo屬性的元素

          E[foo="bar"]    包含屬性foo值為bar的元素


          CSS3 中的選擇器可替代 了解即可

          E[foo~="bar"]  含有屬性foo值包括bar的元素例如 <a foo="bar bar1 bar2">link</a>

          E[foo|="en"]    屬性值是一個“-”分割的 比如 en-us


          E:first-child   父元素的第一個子元素

          E:lang(fr)      匹配屬性,元素顯示內容為語言為 fr

          E::before       在元素前面插入內容

          E::after        在元素后面插入內容

          E>F             子包含

          E+E             相鄰兄弟選擇器 后面的兄弟


          css3


          E[foo^="bar"]   屬性foo的值開頭是bar

          E[foo$="bar"]  屬性foo的值得結尾是bar

          E[foo*="bar"]   屬性foo的值包含bar <a foo="abc_bar_as">link</a>


          結構類選擇器


          E:root          屬性文檔所在的根元素

          E:nth-child(n)  E元素第n個位置的子元素 n可以是 (1,2,3) 關鍵字(odd,even) 公式(2n,2n+3) 起始值為1

          E:nth-last-child(n) 與上面的使用方法一樣 倒數的第N個位置的子元素

          E:nth-of-type(n)    匹配父元素中與E相同的元素中的第n個元素

          E:nth-last-of-type(n)   匹配父元素中與E相同的倒數第n個元素

          E:last-child    選擇位于其父元素的最后一個位置,且匹配E的子元素

          E:first-of-type 選擇在其父元素中匹配E的第一個同類型的子元素

          E:last-of-type  選擇在其父元素中匹配E的最后一個同類型的子元素

          E:only-child    選擇其父元素只包含一個子元素,且該子元素匹配E

          E:only-of-type  選擇其父元素只包含一個同類型的子元素,且該子元素匹配E

          E:empty         選擇匹配E的元素,且該元素不包含子節點,文本也是節點


          E~F 通用兄弟選擇器

          E:not(s)    否定偽類選擇器


          藍藍設計www.syprn.cn )是一家專注而深入的界面設計公司,為期望卓越的國內外企業提供卓越的UI界面設計、BS界面設計 、 cs界面設計 、 ipad界面設計 、 包裝設計 、 圖標定制 、 用戶體驗 、交互設計、 網站建設 、平面設計服務。



          bootstrap table實現x-editable的行單元格編輯及解決數據Empty和支持多樣式

          seo達人

          如果您想訂閱本博客內容,每天自動發到您的郵箱中, 請點這里

          前言

          • 最近在研究bootstrap table的表格的單元格編輯功能,實現點擊單元格修改內容,其中包括文本(text)方式修改,下拉選擇(select)方式修改,日期(date)格式修改等。
          • 本文著重解決x-editable編輯的數據動態添加和顯示數據為Empty的問題,還有給表格單元格的內容設置多樣式,使得顯示多樣化。
          • 由于官網給的demo的數據都是html文件里寫好的,select類型的不能動態添加(所以網上的大多都是官網的類似例子,本篇博客就是在這種情況下以自己的經驗分享給大家,有問題可以留言哦),一旦動態添加就會出現顯示數據為Empty,我表格原本是有數據的,但是一用這個插件就把數據變成Empty了,這可不是我想要的,所以筆者就自行解決了這個問題。

          對比網上的例子

          • 比如以下這種數據不是Empty的例子,但是是由于在html中寫死了數據(awesome),不適合動態添加。
          <a href="#" id="username" data-type="text" data-pk="1">awesome</a> <script> $(function(){ $('#username').editable({
                  url: '/post',
                  title: 'Enter username' });
          }); </script>
              
          • 1
          • 2
          • 3
          • 4
          • 5
          • 6
          • 7
          • 8
          • 9
          • 另外一種就是使用bootstrap table動態添加的,但是select類型就會出現數據為Empty的情況。
          $('#db_dependences').bootstrapTable({
                  method:'POST',
                  dataType:'json',
                  contentType: "application/x-www-form-urlencoded",
                  cache: false,
                  striped: true, //是否顯示行間隔色 sidePagination: "client", //分頁方式:client客戶端分頁,server服務端分頁(*) showColumns:true,
                  pagination:true,
                  minimumCountColumns:2,
                  pageNumber:1, //初始化加載第一頁,默認第一頁 pageSize: 10, //每頁的記錄行數(*) pageList: [10, 15, 20, 25], //可供選擇的每頁的行數(*) uniqueId: "id", //每一行的唯一標識,一般為主鍵列 showExport: true,                    
                  exportDataType: 'all',
                  exportTypes:[ 'csv', 'txt', 'sql', 'doc', 'excel', 'xlsx', 'pdf'], //導出文件類型 onEditableSave: function (field, row, oldValue, $el) { $.ajax({
                          success: function (data, status) { if (status == "success") {
                                  alert("編輯成功");
                              }
                          },
                          error: function () { alert("Error");
                          },
                          complete: function () { }
                      });
                  },
                  data: [{
                      id: 1,
                      name: '張三',
                      sex: '男',
                      time: '2017-08-09' }, {
                      id: 2,
                      name: '王五',
                      sex: '女',
                      time: '2017-08-09' }, {
                      id: 3,
                      name: '李四',
                      sex: '男',
                      time: '2017-08-09' }, {
                      id: 4,
                      name: '楊朝來',
                      sex: '男',
                      time: '2017-08-09' }, {
                      id: 5,
                      name: '蔣平',
                      sex: '男',
                      time: '2017-08-09' }, {
                      id: 6,
                      name: '唐燦華',
                      sex: '男',
                      time: '2017-08-09' }],
                  columns: [{
                      field: 'id',
                      title: '序號' }, {
                      field: 'name',
                      title: '姓名',
                      editable: {
                          type: 'text',  
                          validate: function (value) { if ($.trim(value) == '') { return '姓名不能為空!';  
                              }  
                          }
                      } 
                  }, {
                      field: 'sex',
                      title: '性別',
                      editable: {
                          type: 'select',
                          pk: 1,
                          source: [
                              {value: 1, text: '男'},
                              {value: 2, text: '女'},
                          ]
                      }
                  },  {
                      field: 'time',
                      title: '時間',
                      editable: {
                          type: 'date',
                          format: 'yyyy-mm-dd',    
                          viewformat: 'yyyy-mm-dd',    
                          datepicker: {
                              weekStart: 1 }
                      } 
                  }]
              });
              
          • 1
          • 2
          • 3
          • 4
          • 5
          • 6
          • 7
          • 8
          • 9
          • 10
          • 11
          • 12
          • 13
          • 14
          • 15
          • 16
          • 17
          • 18
          • 19
          • 20
          • 21
          • 22
          • 23
          • 24
          • 25
          • 26
          • 27
          • 28
          • 29
          • 30
          • 31
          • 32
          • 33
          • 34
          • 35
          • 36
          • 37
          • 38
          • 39
          • 40
          • 41
          • 42
          • 43
          • 44
          • 45
          • 46
          • 47
          • 48
          • 49
          • 50
          • 51
          • 52
          • 53
          • 54
          • 55
          • 56
          • 57
          • 58
          • 59
          • 60
          • 61
          • 62
          • 63
          • 64
          • 65
          • 66
          • 67
          • 68
          • 69
          • 70
          • 71
          • 72
          • 73
          • 74
          • 75
          • 76
          • 77
          • 78
          • 79
          • 80
          • 81
          • 82
          • 83
          • 84
          • 85
          • 86
          • 87
          • 88
          • 89
          • 90
          • 91
          • 92
          • 93
          • 94
          • 95
          • 96
          • 97
          • 98
          • 99
          • 100
          • 101

          結果圖如下:

          這里寫圖片描述

          由于開源,很快就找到原因,由于formatter我們沒有寫這個function導致調用的默認的formatter,默認的沒有把表格的值傳入html中,bootstrap-table-editable.js源碼如下,初始定義_dont_edit_formatter為false,我們沒有實現noeditFormatter的function就會執行第二個if語句,其中的標簽中沒有對內容賦值,導致最后顯示結果為它默認的Empty:

          column.formatter = function(value, row, index) { var result = column._formatter ? column._formatter(value, row, index) : value;
          
                          $.each(column, processDataOptions);
          
                          $.each(editableOptions, function(key, value) {
                              editableDataMarkup.push(' ' + key + '="' + value + '"');
                          }); var _dont_edit_formatter = false; if (column.editable.hasOwnProperty('noeditFormatter')) {
                              _dont_edit_formatter = column.editable.noeditFormatter(value, row, index);
                          } if (_dont_edit_formatter === false) { return ['<a href="javascript:void(0)"', ' data-name="' + column.field + '"', ' data-pk="' + row[that.options.idField] + '"', ' data-value="' + result + '"',
                                  editableDataMarkup.join(''), '>' + '</a>' ].join('');
                          } else { return _dont_edit_formatter;
                          }
          
                      };
              
          • 1
          • 2
          • 3
          • 4
          • 5
          • 6
          • 7
          • 8
          • 9
          • 10
          • 11
          • 12
          • 13
          • 14
          • 15
          • 16
          • 17
          • 18
          • 19
          • 20
          • 21
          • 22
          • 23
          • 24
          • 25
          • 26
          • 27

          由于要實現多樣式,則把上面的代碼改變,并在使用的時候實現noeditFormatter:function(value){…}就是了。將上面的代碼改為如下(此為我自己改的,你可以根據自己的需要做修改):

          column.formatter = function(value, row, index) { var result = column._formatter ? column._formatter(value, row, index) : value;
          
                          $.each(column, processDataOptions);
          
                          $.each(editableOptions, function(key, value) {
                              editableDataMarkup.push(' ' + key + '="' + value + '"');
                          }); var _dont_edit_formatter = false; if (column.editable.hasOwnProperty('noeditFormatter')) { var process = column.editable.noeditFormatter(value, row, index); if(!process.hasOwnProperty('class')){
                                  process.class = '';
                              } if(!process.hasOwnProperty('style')){
                                  process.style = '';
                              }
                              _dont_edit_formatter = ['<a href="javascript:void(0)"', ' data-name="'+process.filed+'"', ' data-pk="1"', ' data-value="' + process.value + '"', ' class="'+process.class+'" style="'+process.style+'"', '>' + process.value + '</a>' ].join('');
                          } if (_dont_edit_formatter === false) { return ['<a href="javascript:void(0)"', ' data-name="' + column.field + '"', ' data-pk="' + row[that.options.idField] + '"', ' data-value="' + result + '"',
                                  editableDataMarkup.join(''), '>' + value + '</a>' ].join('');
                          } else { return _dont_edit_formatter;
                          }
          
                      };
              
          • 1
          • 2
          • 3
          • 4
          • 5
          • 6
          • 7
          • 8
          • 9
          • 10
          • 11
          • 12
          • 13
          • 14
          • 15
          • 16
          • 17
          • 18
          • 19
          • 20
          • 21
          • 22
          • 23
          • 24
          • 25
          • 26
          • 27
          • 28
          • 29
          • 30
          • 31
          • 32
          • 33
          • 34
          • 35
          • 36
          • 37
          • 38
          • 39
          • 40

          結果如下:

          這里寫圖片描述

          這里寫圖片描述

          然后是bootstrap table的使用js文件,在其中實現noeditFormatter函數。返回的result必須包含filed和value,class和style可以不需要,class可以額外用其它插件之類,比如badge,style是增加樣式(背景,顏色,字體等)。

          $('#db_dependences').bootstrapTable({
                  method:'POST',
                  dataType:'json',
                  contentType: "application/x-www-form-urlencoded",
                  cache: false,
                  striped: true, //是否顯示行間隔色 sidePagination: "client", //分頁方式:client客戶端分頁,server服務端分頁(*) showColumns:true,
                  pagination:true,
                  minimumCountColumns:2,
                  pageNumber:1, //初始化加載第一頁,默認第一頁 pageSize: 10, //每頁的記錄行數(*) pageList: [10, 15, 20, 25], //可供選擇的每頁的行數(*) uniqueId: "id", //每一行的唯一標識,一般為主鍵列 showExport: true,                    
                  exportDataType: 'all',
                  exportTypes:[ 'csv', 'txt', 'sql', 'doc', 'excel', 'xlsx', 'pdf'], //導出文件類型 onEditableSave: function (field, row, oldValue, $el) { $.ajax({
                          success: function (data, status) { if (status == "success") {
                                  alert("編輯成功");
                              }
                          },
                          error: function () { alert("Error");
                          },
                          complete: function () { }
                      });
                  }, //      onEditableHidden: function(field, row, $el, reason) { // 當編輯狀態被隱藏時觸發 //          if(reason === 'save') { //              var $td = $el.closest('tr').children(); //          //    $td.eq(-1).html((row.price*row.number).toFixed(2)); //          //    $el.closest('tr').next().find('.editable').editable('show'); //編輯狀態向下一行移動 //          } else if(reason === 'nochange') { //              $el.closest('tr').next().find('.editable').editable('show'); //          } //      }, data: [{
                      id: 1,
                      name: '張三',
                      sex: '男',
                      time: '2017-08-09' }, {
                      id: 2,
                      name: '王五',
                      sex: '女',
                      time: '2017-08-09' }, {
                      id: 3,
                      name: '李四',
                      sex: '男',
                      time: '2017-08-09' }, {
                      id: 4,
                      name: '楊朝來',
                      sex: '男',
                      time: '2017-08-09' }, {
                      id: 5,
                      name: '蔣平',
                      sex: '男',
                      time: '2017-08-09' }, {
                      id: 6,
                      name: '唐燦華',
                      sex: '男',
                      time: '2017-08-09' }, {
                      id: 7,
                      name: '馬達',
                      sex: '男',
                      time: '2017-08-09' }, {
                      id: 8,
                      name: '趙小雪',
                      sex: '女',
                      time: '2017-08-09' }, {
                      id: 9,
                      name: '薛文泉',
                      sex: '男',
                      time: '2017-08-09' }, {
                      id: 10,
                      name: '丁建',
                      sex: '男',
                      time: '2017-08-09' }, {
                      id: 11,
                      name: '王麗',
                      sex: '女',
                      time: '2017-08-09' }],
                  columns: [{
                      field: 'id',
                      title: '序號' }, {
                      field: 'name',
                      title: '姓名',
                      editable: {
                          type: 'text',  
                          validate: function (value) { if ($.trim(value) == '') { return '姓名不能為空!';  
                              }  
                          }
                      } 
                  }, {
                      field: 'sex',
                      title: '性別',
                      editable: {
                          type: 'select',
                          pk: 1,
                          source: [
                              {value: 1, text: '男'},
                              {value: 2, text: '女'},
                          ],
                          noeditFormatter: function (value,row,index) { var result={filed:"sex",value:value,class:"badge",style:"background:#333;padding:5px 10px;"}; return result;
                          }
                      }
                  },  {
                      field: 'time',
                      title: '時間',
                      editable: {
                          type: 'date',
                          format: 'yyyy-mm-dd',    
                          viewformat: 'yyyy-mm-dd',    
                          datepicker: {
                              weekStart: 1 },
                          noeditFormatter: function (value,row,index) { var result={filed:"time",value:value,class:"badge",style:"background:#333;padding:5px 10px;"}; return result;
                          }
                      } 
                  }]
              });
              
          • 1
          • 2
          • 3
          • 4
          • 5
          • 6
          • 7
          • 8
          • 9
          • 10
          • 11
          • 12
          • 13
          • 14
          • 15
          • 16
          • 17
          • 18
          • 19
          • 20
          • 21
          • 22
          • 23
          • 24
          • 25
          • 26
          • 27
          • 28
          • 29
          • 30
          • 31
          • 32
          • 33
          • 34
          • 35
          • 36
          • 37
          • 38
          • 39
          • 40
          • 41
          • 42
          • 43
          • 44
          • 45
          • 46
          • 47
          • 48
          • 49
          • 50
          • 51
          • 52
          • 53
          • 54
          • 55
          • 56
          • 57
          • 58
          • 59
          • 60
          • 61
          • 62
          • 63
          • 64
          • 65
          • 66
          • 67
          • 68
          • 69
          • 70
          • 71
          • 72
          • 73
          • 74
          • 75
          • 76
          • 77
          • 78
          • 79
          • 80
          • 81
          • 82
          • 83
          • 84
          • 85
          • 86
          • 87
          • 88
          • 89
          • 90
          • 91
          • 92
          • 93
          • 94
          • 95
          • 96
          • 97
          • 98
          • 99
          • 100
          • 101
          • 102
          • 103
          • 104
          • 105
          • 106
          • 107
          • 108
          • 109
          • 110
          • 111
          • 112
          • 113
          • 114
          • 115
          • 116
          • 117
          • 118
          • 119
          • 120
          • 121
          • 122
          • 123
          • 124
          • 125
          • 126
          • 127
          • 128
          • 129
          • 130
          • 131
          • 132
          • 133
          • 134
          • 135
          • 136
          • 137
          • 138
          • 139
          • 140
          • 141
          • 142
          • 143

          關于bootstrap table的導出及使用可以看我另外一篇博客。

          下載和引用

          下載x-editable,并如下引用。

          <link href="js/bootstrap_above/x-editable-develop/dist/bootstrap-editable/css/bootstrap-editable.css" rel="stylesheet"> <script src="js/bootstrap_above/x-editable-develop/dist/bootstrap-editable/js/bootstrap-editable.js"></script> <script src="js/bootstrap_above/bootstrap-table-develop/dist/extensions/editable/bootstrap-table-editable.js"></script>
              
          • 1
          • 2
          • 3

          然后講上訴的一些文件修改添加,就完成了。

          另外項目的結果展示

          這里寫圖片描述

          這里寫圖片描述

          其中的樣式都是自行在x-editable的基礎上添加的。如配置出問題,以下是源碼鏈接。

          藍藍設計www.syprn.cn )是一家專注而深入的界面設計公司,為期望卓越的國內外企業提供卓越的UI界面設計、BS界面設計 、 cs界面設計 、 ipad界面設計 、 包裝設計 、 圖標定制 、 用戶體驗 、交互設計、 網站建設 平面設計服務。


          HTML2.1表單標簽及屬性介紹

          seo達人

          如果您想訂閱本博客內容,每天自動發到您的郵箱中, 請點這里

          <!DOCTYPE html>

          <html>

             <head>

                 <meta charset="UTF-8">

                 <title>表單標簽及屬性介紹</title>

             </head>

             <body>

             <!--form:表單標簽,在html頁面創建一個表單(瀏覽器上不顯示),若要提交數據到服務器則負責收集數據的標簽要放到form內 ;-->

                 <!--action:(確定表單提交的路徑);-->

                 <!--method提交方式:get(默認值)有內容 ,post沒有-->

                 <form action="#" method="get">


                     <!--input:輸入域標簽,獲取用戶輸入信息;-->

                     <!--type值不同收集方式不同:hidden(隱藏字段,數據會發送到服務器但瀏覽器不顯示),text(文本框),password(密碼框),radio(單選框),checkbox(復選框),

                         file(文件上傳組件),submit(提交按鈕),button(普通按鈕),reset(重置按鈕);-->

                     <!--name:元素名(表單數據需提交到服務器必提供name屬性值),服務器通過屬性值獲取提交數據;-->

                     <!--readonly:只讀-->

                     <!--value:設置input默認值。submit和reset為按鍵上顯示數據-->

                     <!--size:大小-->

                     <!--maxlength:允許輸入的最大長度-->

                     隱藏字段:<input type="hidden" name="id" value=""/><br/>

                     用戶名:<input type="text" name="username" readonly="readonly" value="zhangsan" size="40px" maxlength="20"/><br/>

                     密碼:<input type="password" name="password"/><br/>

                     確認密碼:<input type="password" name="repassword"/><br/>

                     性別:<input type="radio" name="sex" value="man"/>男

                     <input type="radio" name="sex" value="woman"/>女<br/>

                     愛好:<input type="checkbox" name="hobby" value="釣魚"/>釣魚

                     <input type="checkbox" name="hobby" value="打電動"/>打電動

                     <!--checked:單選或復選框默認勾選-->

                     <input type="checkbox" name="hobby" value="畫畫" checked="checked"/>畫畫<br/>

                     頭像:<input type="file" /><br/>

                     <!--select:下拉列表標簽-->

                     籍貫:<select name="province">

                         <!--option:子標簽,下拉列表中的一個選項-->

                         <option>---請選擇---</option>

                         <!--value:發送得服務器的選項值-->

                         <option value="北京">北京</option>

                         <option value="上海">上海</option>

                         <!--selected:勾選當前列表項-->

                         <option value="廣州" selected="selected">廣州</option>

                     </select><br/>

                     自我介紹:

                         <!--textarea:文本域-->

                         <textarea>


                         </textarea><br/>

                     提交按鈕:<input type="submit" value="注冊"/><br/>

                     普通按鈕:<input type="button" value="普通按鈕"><br/>

                     重置按鈕:<input type="reset"/>

                 </form>

             </body>

          </html>


          藍藍設計www.syprn.cn )是一家專注而深入的界面設計公司,為期望卓越的國內外企業提供卓越的UI界面設計、BS界面設計 、 cs界面設計 、 ipad界面設計 、 包裝設計 、 圖標定制 、 用戶體驗 、交互設計、 網站建設 、平面設計服務。


          Android MVP極限封裝(一)

          seo達人

          如果您想訂閱本博客內容,每天自動發到您的郵箱中, 請點這里

          MVP架構在Android這一塊已經盛行依舊,對于一些學習能力比較強的人來說,已經能夠運用自如甚至改造優化了,對于吾等菜鳥,卻是如此的陌生,今日這篇博客,算是小弟在學習和應用上的一點總結罷了,如有不足,還請各位大神不吝指教。

          MVP架構是什么就不多說了,博主主要很大家分享的是,如何設計MVP架構。

          先來分析一下MVP如何使用:M-V-P三層之間,P作為中間層,負責M,V之間的數據交互的中介,將數據從M層獲取,處理之后提交到V層,換句話說,V需要持有P的實例,P層需要持有V的實例。原理很簡單,使用泛型對數據進行封裝處理: 
          1.定義一個V層的空接口,主要是方便封裝:

          /**
           * V層接口
           */ public interface IView { }
                      
          • 1
          • 2
          • 3
          • 4
          • 5

          2.定義一個P層的接口:

          /**
           * 抽象為接口
           * 
           */ public interface IPresenter<V extends IView> { /**
               * 綁定視圖
               * 
               * @param view
               */ void attachView(V view); /**
               * 解除綁定(每個V記得使用完之后解綁,主要是用于防止內存泄漏問題)
               */ void dettachView();
          
          }
                      
          • 1
          • 2
          • 3
          • 4
          • 5
          • 6
          • 7
          • 8
          • 9
          • 10
          • 11
          • 12
          • 13
          • 14
          • 15
          • 16
          • 17
          • 18
          • 19

          3.封裝P基類:綁定解綁V實例

          /**
           * 抽象類 統一管理View層綁定和解除綁定
           *
           * @param <V>
           */ public class BasePresenter<V extends IView, M extends IModel> implements IPresenter<V> { private WeakReference<V> weakView; protected M model;
          
              public V getView() { return proxyView;
              } /**
               * 用于檢查View是否為空對象
               *
               * @return */ public boolean isAttachView() { return this.weakView != null && this.weakView.get() != null;
              } @Override public void attachView(V view) { this.weakView = new WeakReference<V>(view);
              } @Override public void dettachView() { if (this.weakView != null) { this.weakView.clear(); this.weakView = null;
                  }
              }
          }
                      
          • 1
          • 2
          • 3
          • 4
          • 5
          • 6
          • 7
          • 8
          • 9
          • 10
          • 11
          • 12
          • 13
          • 14
          • 15
          • 16
          • 17
          • 18
          • 19
          • 20
          • 21
          • 22
          • 23
          • 24
          • 25
          • 26
          • 27
          • 28
          • 29
          • 30
          • 31
          • 32
          • 33
          • 34
          • 35
          • 36

          4.M層封裝:

          /**
           * M層
           */ public interface IModel { } /**
           * 登錄model
           * Created by admin on 2018/2/5.
           */ public interface ILoginModel extends IModel { void login();
          } /**
           * 登錄
           * Created by admin on 2018/2/5.
           */ public class LoginModel implements ILoginModel { @Override public void login() { // TODO: 2018/2/5 發起登錄請求  }
          }
                      
          • 1
          • 2
          • 3
          • 4
          • 5
          • 6
          • 7
          • 8
          • 9
          • 10
          • 11
          • 12
          • 13
          • 14
          • 15
          • 16
          • 17
          • 18
          • 19
          • 20
          • 21
          • 22
          • 23
          • 24
          • 25

          之后,將數據提交到activity或者fragment就行了。 
          最基本的鋪墊已經做好了,接下來就該封裝View了:

          /**
           * Created by admin on 2018/2/5.
           */ public abstract class MvpActivity<V extends IView, P extends BasePresenter<V>> extends AppCompatActivity implements IView { private P presenter;
          
              @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState);
                  ...
                  presenter=getPresenter();
                  presenter.attachView(this);
              } protected P getPresenter() { return presenter;
              } protected void setPresenter(P presenter) { this.presenter = presenter;
              } protected V getView() { return (V) this;
              }
              ...
              @Override protected void onDestroy() {
                  presenter.dettachView();
                  ... super.onDestroy();
              }
          }
                      
          • 1
          • 2
          • 3
          • 4
          • 5
          • 6
          • 7
          • 8
          • 9
          • 10
          • 11
          • 12
          • 13
          • 14
          • 15
          • 16
          • 17
          • 18
          • 19
          • 20
          • 21
          • 22
          • 23
          • 24
          • 25
          • 26
          • 27
          • 28
          • 29
          • 30
          • 31
          • 32
          • 33
          • 34
          • 35

          收工,MVP基礎框架搭建完成了。沒錯,就是基礎框架,但是能不能用呢,讓我們拭目以待吧。 
          先來寫一個View:

          public interface ILoginView extends IView { void onLoginSuccess(); void onFailed();
          
          }
                      
          • 1
          • 2
          • 3
          • 4
          • 5
          • 6

          然后是Presneter:

          /**
           * Created by admin on 2018/2/5.
           */ public class LoginPresenter extends BasePresenter<ILogin, LoginModel> { public LoginPresenter() {
                  model = new LoginModel();
              }
          
              public void login(){
                  model.login(new LoginCallBack() { @Override public void onSuccess() { if(null!=(ILogin)getView()){
                              weakView.onLoginSuccess();
                          }
                      } @Override public void onFailure() { if(null!=(ILogin)getView()){
                              weakView.onFailure();
                          }
                      }
                  });
              }
          
          }
                      
          • 1
          • 2
          • 3
          • 4
          • 5
          • 6
          • 7
          • 8
          • 9
          • 10
          • 11
          • 12
          • 13
          • 14
          • 15
          • 16
          • 17
          • 18
          • 19
          • 20
          • 21
          • 22
          • 23
          • 24
          • 25
          • 26
          • 27
          • 28

          最后來完成Activity的邏輯:

          public class LoginActivity extends MvpActivity<ILoginView, LoginPresenter> implements ILoginView { ...
              @Override public LoginPresenter getPresenter() { return new LoginPresenter();
              } public void login(View view) {
                  String name = etUserName.getText().toString();
                  String pwd = etUserPwd.getText().toString();
                  getPresenter().login(name, pwd);
              }
          
              @Override public void onLoginSuccess() {
          
              }
          
              @Override public void onFailed(){
          
              ...
          }

          
              



          
              


          藍藍設計www.syprn.cn )是一家專注而深入的界面設計公司,為期望卓越的國內外企業提供卓越的UI界面設計、BS界面設計 、 cs界面設計 、 ipad界面設計 、 包裝設計 、 圖標定制 、 用戶體驗 、交互設計、 網站建設 平面設計服務。


          Retrofit源碼分析

          seo達人

          如果您想訂閱本博客內容,每天自動發到您的郵箱中, 請點這里

          1、簡介

          retrofit是一個封裝okhttp請求的網絡請求庫,可以通過Rxjava適配返回信息。

          2、原理分析

          我們通過Retrofit.Builder建造者模式創建一個Retrofit實例對象

          public static final class Builder {
              /**
                *Android線程切換的類 
                */
              private final Platform platform;
              private @Nullable okhttp3.Call.Factory callFactory;
              private HttpUrl baseUrl;
              private final List<Converter.Factory> converterFactories = new ArrayList<>();
              private final List<CallAdapter.Factory> callAdapterFactories = new ArrayList<>();
              private @Nullable Executor callbackExecutor;
              private boolean validateEagerly;
          
              Builder(Platform platform) {
                this.platform = platform;
              }
          
              public Builder() {
                this(Platform.get());
              }
          
              Builder(Retrofit retrofit) {
                platform = Platform.get();
                callFactory = retrofit.callFactory;
                baseUrl = retrofit.baseUrl;
          
                converterFactories.addAll(retrofit.converterFactories);
                // Remove the default BuiltInConverters instance added by build().
                converterFactories.remove(0);
          
                callAdapterFactories.addAll(retrofit.callAdapterFactories);
                // Remove the default, platform-aware call adapter added by build().
                callAdapterFactories.remove(callAdapterFactories.size() - 1);
          
                callbackExecutor = retrofit.callbackExecutor;
                validateEagerly = retrofit.validateEagerly;
              }
          
              public Builder client(OkHttpClient client) {
                return callFactory(checkNotNull(client, "client == null"));
              }
          
              public Builder callFactory(okhttp3.Call.Factory factory) {
                this.callFactory = checkNotNull(factory, "factory == null");
                return this;
              }
          
              public Builder baseUrl(String baseUrl) {
                checkNotNull(baseUrl, "baseUrl == null");
                HttpUrl httpUrl = HttpUrl.parse(baseUrl);
                if (httpUrl == null) {
                  throw new IllegalArgumentException("Illegal URL: " + baseUrl);
                }
                return baseUrl(httpUrl);
              }
          
              public Builder baseUrl(HttpUrl baseUrl) {
                checkNotNull(baseUrl, "baseUrl == null");
                List<String> pathSegments = baseUrl.pathSegments();
                if (!"".equals(pathSegments.get(pathSegments.size() - 1))) {
                  throw new IllegalArgumentException("baseUrl must end in /: " + baseUrl);
                }
                this.baseUrl = baseUrl;
                return this;
              }
          
              public Builder addConverterFactory(Converter.Factory factory) {
                converterFactories.add(checkNotNull(factory, "factory == null"));
                return this;
              }
          
              public Builder addCallAdapterFactory(CallAdapter.Factory factory) {
                callAdapterFactories.add(checkNotNull(factory, "factory == null"));
                return this;
              }
          
              public Builder callbackExecutor(Executor executor) {
                this.callbackExecutor = checkNotNull(executor, "executor == null");
                return this;
              }
          
              public List<CallAdapter.Factory> callAdapterFactories() {
                return this.callAdapterFactories;
              }
          
              public List<Converter.Factory> converterFactories() {
                return this.converterFactories;
              }
          
              public Builder validateEagerly(boolean validateEagerly) {
                this.validateEagerly = validateEagerly;
                return this;
              }
          
              public Retrofit build() {
                if (baseUrl == null) {
                  throw new IllegalStateException("Base URL required.");
                }
          
                okhttp3.Call.Factory callFactory = this.callFactory;
                if (callFactory == null) {
                  callFactory = new OkHttpClient();
                }
          
                Executor callbackExecutor = this.callbackExecutor;
                if (callbackExecutor == null) {
                  callbackExecutor = platform.defaultCallbackExecutor();
                }
          
                // Make a defensive copy of the adapters and add the default Call adapter.
                List<CallAdapter.Factory> callAdapterFactories = new ArrayList<>(this.callAdapterFactories);
                callAdapterFactories.add(platform.defaultCallAdapterFactory(callbackExecutor));
          
                // Make a defensive copy of the converters.
                List<Converter.Factory> converterFactories =
                    new ArrayList<>(1 + this.converterFactories.size());
          
                // Add the built-in converter factory first. This prevents overriding its behavior but also
                // ensures correct behavior when using converters that consume all types.
                converterFactories.add(new BuiltInConverters());
                converterFactories.addAll(this.converterFactories);
          
                return new Retrofit(callFactory, baseUrl, unmodifiableList(converterFactories),
                    unmodifiableList(callAdapterFactories), callbackExecutor, validateEagerly);
              }
           } 
              
          • 1
          • 2
          • 3
          • 4
          • 5
          • 6
          • 7
          • 8
          • 9
          • 10
          • 11
          • 12
          • 13
          • 14
          • 15
          • 16
          • 17
          • 18
          • 19
          • 20
          • 21
          • 22
          • 23
          • 24
          • 25
          • 26
          • 27
          • 28
          • 29
          • 30
          • 31
          • 32
          • 33
          • 34
          • 35
          • 36
          • 37
          • 38
          • 39
          • 40
          • 41
          • 42
          • 43
          • 44
          • 45
          • 46
          • 47
          • 48
          • 49
          • 50
          • 51
          • 52
          • 53
          • 54
          • 55
          • 56
          • 57
          • 58
          • 59
          • 60
          • 61
          • 62
          • 63
          • 64
          • 65
          • 66
          • 67
          • 68
          • 69
          • 70
          • 71
          • 72
          • 73
          • 74
          • 75
          • 76
          • 77
          • 78
          • 79
          • 80
          • 81
          • 82
          • 83
          • 84
          • 85
          • 86
          • 87
          • 88
          • 89
          • 90
          • 91
          • 92
          • 93
          • 94
          • 95
          • 96
          • 97
          • 98
          • 99
          • 100
          • 101
          • 102
          • 103
          • 104
          • 105
          • 106
          • 107
          • 108
          • 109
          • 110
          • 111
          • 112
          • 113
          • 114
          • 115
          • 116
          • 117
          • 118
          • 119
          • 120
          • 121
          • 122
          • 123
          • 124
          • 125
          • 126
          • 127
          • 128
          • 129

          通過Retrofit.Builder中build方法創建一個Retrofit實例對象,在創建Retrofit時會判斷用戶創建OkhttpClient對象,沒有創建Retrofit會創建一個默認okhttpClient對象,然后設置Platform中的主線程線程池,設置線程池處理器交給主線程Looper對象。然后創建一個Retrofit對象。我們通過Retrofit.create創建一個接口代理類

           public <T> T create(final Class<T> service) {
              Utils.validateServiceInterface(service);
              if (validateEagerly) {
                eagerlyValidateMethods(service);
              }
              return (T) Proxy.newProxyInstance(service.getClassLoader(), new Class<?>[] { service },
                  new InvocationHandler() {
                    private final Platform platform = Platform.get();
          
                    @Override public Object invoke(Object proxy, Method method, @Nullable Object[] args)
                        throws Throwable {
                      // If the method is a method from Object then defer to normal invocation.
                      if (method.getDeclaringClass() == Object.class) {
                        return method.invoke(this, args);
                      }
                      if (platform.isDefaultMethod(method)) {
                        return platform.invokeDefaultMethod(method, service, proxy, args);
                      }
                      ServiceMethod<Object, Object> serviceMethod =
                          (ServiceMethod<Object, Object>) loadServiceMethod(method);
                      OkHttpCall<Object> okHttpCall = new OkHttpCall<>(serviceMethod, args);
                      return serviceMethod.adapt(okHttpCall);
                    }
                  });
            } 
              
          • 1
          • 2
          • 3
          • 4
          • 5
          • 6
          • 7
          • 8
          • 9
          • 10
          • 11
          • 12
          • 13
          • 14
          • 15
          • 16
          • 17
          • 18
          • 19
          • 20
          • 21
          • 22
          • 23
          • 24
          • 25
          • 26

          在調用Creater方法時,通過代理類創建Service實例對象,當我們通過接口實例對象調用方法時,通過invoke方法時,通過Method創建一個ServiceMethod對象,然后把ServiceMethod存儲起來

           public ServiceMethod build() {
                    callAdapter = createCallAdapter();
                    responseType = callAdapter.responseType();
                    if (responseType == Response.class || responseType == okhttp3.Response.class) {
                      throw methodError("'"
                          + Utils.getRawType(responseType).getName()
                          + "' is not a valid response body type. Did you mean ResponseBody?");
                    }
                    responseConverter = createResponseConverter();
          
                    for (Annotation annotation : methodAnnotations) {
                      parseMethodAnnotation(annotation);
                    }
          
                    if (httpMethod == null) {
                      throw methodError("HTTP method annotation is required (e.g., @GET, @POST, etc.).");
                    }
          
                    if (!hasBody) {
                      if (isMultipart) {
                        throw methodError(
                            "Multipart can only be specified on HTTP methods with request body (e.g., @POST).");
                      }
                      if (isFormEncoded) {
                        throw methodError("FormUrlEncoded can only be specified on HTTP methods with "
                            + "request body (e.g., @POST).");
                      }
                    }
          
                    int parameterCount = parameterAnnotationsArray.length;
                    parameterHandlers = new ParameterHandler<?>[parameterCount];
                    for (int p = 0; p < parameterCount; p++) {
                      Type parameterType = parameterTypes[p];
                      if (Utils.hasUnresolvableType(parameterType)) {
                        throw parameterError(p, "Parameter type must not include a type variable or wildcard: %s",
                            parameterType);
                      }
          
                      Annotation[] parameterAnnotations = parameterAnnotationsArray[p];
                      if (parameterAnnotations == null) {
                        throw parameterError(p, "No Retrofit annotation found.");
                      }
          
                      parameterHandlers[p] = parseParameter(p, parameterType, parameterAnnotations);
                    }
          
                    if (relativeUrl == null && !gotUrl) {
                      throw methodError("Missing either @%s URL or @Url parameter.", httpMethod);
                    }
                    if (!isFormEncoded && !isMultipart && !hasBody && gotBody) {
                      throw methodError("Non-body HTTP method cannot contain @Body.");
                    }
                    if (isFormEncoded && !gotField) {
                      throw methodError("Form-encoded method must contain at least one @Field.");
                    }
                    if (isMultipart && !gotPart) {
                      throw methodError("Multipart method must contain at least one @Part.");
                    }
          
                    return new ServiceMethod<>(this);
                  }
          
              private CallAdapter<T, R> createCallAdapter() {
                      /**
                       *獲取方法返回值類型
                       */
                    Type returnType = method.getGenericReturnType();
                    if (Utils.hasUnresolvableType(returnType)) {
                      throw methodError(
                          "Method return type must not include a type variable or wildcard: %s", returnType);
                    }
                    if (returnType == void.class) {
                      throw methodError("Service methods cannot return void.");
                    }
                    //獲取注解信息
                    Annotation[] annotations = method.getAnnotations();
                    try {
                      //noinspection unchecked
                      return (CallAdapter<T, R>) retrofit.callAdapter(returnType, annotations);
                    } catch (RuntimeException e) { // Wide exception range because factories are user code.
                      throw methodError(e, "Unable to create call adapter for %s", returnType);
                    }
                  } 
              
          • 1
          • 2
          • 3
          • 4
          • 5
          • 6
          • 7
          • 8
          • 9
          • 10
          • 11
          • 12
          • 13
          • 14
          • 15
          • 16
          • 17
          • 18
          • 19
          • 20
          • 21
          • 22
          • 23
          • 24
          • 25
          • 26
          • 27
          • 28
          • 29
          • 30
          • 31
          • 32
          • 33
          • 34
          • 35
          • 36
          • 37
          • 38
          • 39
          • 40
          • 41
          • 42
          • 43
          • 44
          • 45
          • 46
          • 47
          • 48
          • 49
          • 50
          • 51
          • 52
          • 53
          • 54
          • 55
          • 56
          • 57
          • 58
          • 59
          • 60
          • 61
          • 62
          • 63
          • 64
          • 65
          • 66
          • 67
          • 68
          • 69
          • 70
          • 71
          • 72
          • 73
          • 74
          • 75
          • 76
          • 77
          • 78
          • 79
          • 80
          • 81
          • 82
          • 83
          • 84
          • 85

          在創建ServiceMethod時,獲取我們okhttp請求是否有返回值,沒有返回值拋出異常,然后獲取注解信息,然后獲取retrofit中CallAdapter.Factory,然后調用get方法,我們在通過rxjavaFactoryAdapter.create創建的就是實現CallAdapter.Factory對象,然后調用CallAdapter.Factory中respenseType方法,然后通過我們傳遞converter對數據進行序列化,可以通過gson和fastjson進行實例化對象,然后通過parseMethodAnnomation解析請求類型

           private void parseHttpMethodAndPath(String httpMethod, String value, boolean hasBody) {
                    if (this.httpMethod != null) {
                      throw methodError("Only one HTTP method is allowed. Found: %s and %s.",
                          this.httpMethod, httpMethod);
                    }
                    this.httpMethod = httpMethod;
                    this.hasBody = hasBody;
          
                    if (value.isEmpty()) {
                      return;
                    }
          
                    // Get the relative URL path and existing query string, if present.
                    int question = value.indexOf('?');
                    if (question != -1 && question < value.length() - 1) {
                      // Ensure the query string does not have any named parameters.
                      String queryParams = value.substring(question + 1);
                      Matcher queryParamMatcher = PARAM_URL_REGEX.matcher(queryParams);
                      if (queryParamMatcher.find()) {
                        throw methodError("URL query string \"%s\" must not have replace block. "
                            + "For dynamic query parameters use @Query.", queryParams);
                      }
                    }
          
                    this.relativeUrl = value;
                    this.relativeUrlParamNames = parsePathParameters(value);
                  } 
              
          • 1
          • 2
          • 3
          • 4
          • 5
          • 6
          • 7
          • 8
          • 9
          • 10
          • 11
          • 12
          • 13
          • 14
          • 15
          • 16
          • 17
          • 18
          • 19
          • 20
          • 21
          • 22
          • 23
          • 24
          • 25
          • 26
          • 27
          • 28

          通過注解類型獲取到請求類型時,通過調用相關方法解析獲取到請求url,然后通過注解獲取方法中是否有注解字段,有注解信息存儲到Set集合中。然后創建一個OkhttpCall對象,通過調用serviceMethod.adapt方法做網絡請求,serviceMethod.adapt調用是callAdapter中的adapt方法,如果用戶沒有設置callAdapter模式使用的是ExecutorCallAdapterFactory中的adapt方法

           public CallAdapter<?, ?> get(Type returnType, Annotation[] annotations, Retrofit retrofit) {
                      if (getRawType(returnType) != Call.class) {
                          return null;
                      } else {
                          final Type responseType = Utils.getCallResponseType(returnType);
                          return new CallAdapter<Object, Call<?>>() {
                              public Type responseType() {
                                  return responseType;
                              }
          
                              public Call<Object> adapt(Call<Object> call) {
                                  return new ExecutorCallAdapterFactory.ExecutorCallbackCall(ExecutorCallAdapterFactory.this.callbackExecutor, call);
                              }
                          };
                      }
                  } 
              
          • 1
          • 2
          • 3
          • 4
          • 5
          • 6
          • 7
          • 8
          • 9
          • 10
          • 11
          • 12
          • 13
          • 14
          • 15
          • 16
          • 17

          在ExectorCallAdapterFactory中調用組裝的Call方法中enqueue方法調用異步網絡請求,成功后通過Platform中MainThreadExecutor切換到主線程。在調用callback中的enqueue,onResponse和onFairlure方法時實際是調用到OkhttpCall方法的onResponse方法,在OkHttpCall.enqueue中重新組建OkHttp.Call url和參數信息,然后封裝請求,請求成功后通過parseResponse解析返回信息狀態,然后把返回信息狀態成ResponseBody對象,調用ServiceMethod.toResponse解析,在toResponse中實際是我們設置ConverterFactory對象解析數據,完成后調用callBack中onSuccess方法。

           @Override public void enqueue(final Callback<T> callback) {
                  checkNotNull(callback, "callback == null");
          
                  okhttp3.Call call;
                  Throwable failure;
          
                  synchronized (this) {
                    if (executed) throw new IllegalStateException("Already executed.");
                    executed = true;
          
                    call = rawCall;
                    failure = creationFailure;
                    if (call == null && failure == null) {
                      try {
                        call = rawCall = createRawCall();
                      } catch (Throwable t) {
                        throwIfFatal(t);
                        failure = creationFailure = t;
                      }
                    }
                  }
          
                  if (failure != null) {
                    callback.onFailure(this, failure);
                    return;
                  }
          
                  if (canceled) {
                    call.cancel();
                  }
          
                  call.enqueue(new okhttp3.Callback() {
                    @Override public void onResponse(okhttp3.Call call, okhttp3.Response rawResponse) {
                      Response<T> response;
                      try {
                        response = parseResponse(rawResponse);
                      } catch (Throwable e) {
                        callFailure(e);
                        return;
                      }
          
                      try {
                        callback.onResponse(OkHttpCall.this, response);
                      } catch (Throwable t) {
                        t.printStackTrace();
                      }
                    }
          
                    @Override public void onFailure(okhttp3.Call call, IOException e) {
                      callFailure(e);
                    }
          
                    private void callFailure(Throwable e) {
                      try {
                        callback.onFailure(OkHttpCall.this, e);
                      } catch (Throwable t) {
                        t.printStackTrace();
                      }
                    }
                  });
                }
          藍藍設計www.syprn.cn )是一家專注而深入的界面設計公司,為期望卓越的國內外企業提供卓越的UI界面設計、BS界面設計 、 cs界面設計 、 ipad界面設計 、 包裝設計 、 圖標定制 、 用戶體驗 、交互設計、 網站建設 、平面設計服務。

          PYTHON爬蟲——必應圖片關鍵詞爬取

          seo達人

          如果您想訂閱本博客內容,每天自動發到您的郵箱中, 請點這里

          圖片三個網站的圖片搜索結果進行爬取和下載。 
          首先通過爬蟲過程中遇到的問題,總結如下: 
          1、一次頁面加載的圖片數量各個網站是不定的,每翻一頁就會刷新一次,對于數據量大的爬蟲幾乎都需要用到翻頁功能,有如下兩種方式: 
          1)通過網站上的網址進行刷新,例如必應圖片:

          url = 'http://cn.bing.com/images/async?q={0}&first={1}&count=35&relp=35&lostate=r
          &mmasync=1&dgState=x*175_y*848_h*199_c*1_i*106_r*0'
              
          • 1
          • 2

          2)通過selenium來實現模擬鼠標操作來進行翻頁,這一點會在Google圖片爬取的時候進行講解。 
          2、每個網站應用的圖片加載技術都不一樣,對于靜態加載的網站爬取圖片非常容易,因為每張圖片的url都直接顯示在網頁源碼中,找到每張圖片對應的url即可使用urlretrieve()進行下載。然而對于動態加載的網站就比較復雜,需要具體問題具體分析,例如google圖片每次就會加載35張圖片(只能得到35張圖片的url),當滾動一次后網頁并不刷新但是會再次加載一批圖片,與前面加載完成的都一起顯示在網頁源碼中。對于動態加載的網站我推薦使用selenium庫來爬取。

          對于爬取圖片的流程基本如下(對于可以通過網址實現翻頁或者無需翻頁的網站): 
          1. 找到你需要爬取圖片的網站。(以必應為例)

          這里寫圖片描述
          2. 使用google元素檢查(其他的沒用過不做介紹)來查看網頁源碼。

          這里寫圖片描述
          3. 使用左上角的元素檢查來找到對應圖片的代碼。

          這里寫圖片描述
          4. 通過觀察找到翻頁的規律(有些網站的動態加載是完全看不出來的,這種方法不推薦)

          這里寫圖片描述
          從圖中可以看到標簽div,class=’dgControl hover’中的data-nexturl的內容隨著我們滾動頁面翻頁first會一直改變,q=二進制碼即我們關鍵字的二進制表示形式。加上前綴之后由此我們才得到了我們要用的url。 
          5. 我們將網頁的源碼放進BeautifulSoup中,代碼如下:

          url = 'http://cn.bing.com/images/async?q={0}&first={1}&count=35&relp=35&lostate=r&mmasync=1&dgState=x*175_y*848_h*199_c*1_i*106_r*0' agent = {'User-Agent': "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/31.0.165063 Safari/537.36 AppEngine-Google."}
          page1 = urllib.request.Request(url.format(InputData, i*35+1), headers=agent)
          page = urllib.request.urlopen(page1)
          soup = BeautifulSoup(page.read(), 'html.parser')
              
          • 1
          • 2
          • 3
          • 4
          • 5

          我們得到的soup是一個class ‘bs4.BeautifulSoup’對象,可以直接對其進行操作,具體內容自行查找。 
          首先選取我們需要的url所在的class,如下圖: 
          這里寫圖片描述
          波浪線是我們需要的url。 
          我們由下面的代碼得到我們需要的url:

          if not os.path.exists("./" + word):#創建文件夾 os.mkdir('./' + word) for StepOne in soup.select('.mimg'):
              link=StepOne.attrs['src']#將得到的<class 'bs4.element.Tag'>轉化為字典形式并取src對應的value。 count = len(os.listdir('./' + word)) + 1 SaveImage(link,word,count)#調用函數保存得到的圖片。
              
          • 1
          • 2
          • 3
          • 4
          • 5
          • 6
          • 7

          最后調用urlretrieve()函數下載我們得到的圖片url,代碼如下:

           try:
                  time.sleep(0.2)
                  urllib.request.urlretrieve(link,'./'+InputData+'/'+str(count)+'.jpg') except urllib.error.HTTPError as urllib_err:
                  print(urllib_err) except Exception as err:
                  time.sleep(1)
                  print(err)
                  print("產生未知錯誤,放棄保存") else:
                  print("圖+1,已有" + str(count) + "張圖")
              
          • 1
          • 2
          • 3
          • 4
          • 5
          • 6
          • 7
          • 8
          • 9
          • 10
          • 11

          這里需要強調是像前面的打開網址和現在的下載圖片都需要使用try except進行錯誤測試,否則出錯時程序很容易崩潰,大大浪費了數據采集的時間。 
          以上就是對單個頁面進行數據采集的流程,緊接著改變url中{1}進行翻頁操作繼續采集下一頁。 
          數據采集結果如下: 
          這里寫圖片描述

          有問題請留言。 

          藍藍設計www.syprn.cn )是一家專注而深入的界面設計公司,為期望卓越的國內外企業提供卓越的UI界面設計、BS界面設計 、 cs界面設計 、 ipad界面設計 、 包裝設計 、 圖標定制 、 用戶體驗 、交互設計、 網站建設 、平面設計服務。

          微信小程序實現倒計時,蘋果手機不顯示

          seo達人

          如果您想訂閱本博客內容,每天自動發到您的郵箱中, 請點這里

          JS頁面代碼段:

          
              
          1. const app = getApp()
          2. let goodsList = [
          3. { actEndTime: '2018-07-21 21:00:34' },
          4. { actEndTime: '2028-07-17 21:00:37' },
          5. { actEndTime: '2018-09-21 05:00:59' },
          6. { actEndTime: '2018-08-19 07:00:48' },
          7. { actEndTime: '2018-08-28 03:00:11' }
          8. ]
          9. Page({
          10. data: {
          11. countDownList: [],
          12. actEndTimeList: []
          13. },
          14. onLoad: function () {
          15. let endTimeList = [];
          16. // 將活動的結束時間參數提成一個單獨的數組,方便操作
          17. goodsList.forEach(o => { endTimeList.push(o.actEndTime) })
          18. this.setData({ actEndTimeList: endTimeList });
          19. // 執行倒計時函數
          20. this.countDown();
          21. },
          22. //當時間小于兩位數時十位數補零。
          23. timeFormat: function (param) {//小于10的格式化函數
          24. return param < 10 ? '0' + param : param;
          25. },
          26. //倒計時函數
          27. countDown: function () {
          28. // 獲取當前時間,同時得到活動結束時間數組
          29. let newTime = new Date().getTime();//當前時間
          30. let endTimeList = this.data.actEndTimeList;//結束時間的數組集合
          31. let countDownArr = [];//初始化倒計時數組
          32. // 對結束時間進行處理渲染到頁面
          33. endTimeList.forEach(o => {
          34. let endTime = new Date(o).getTime();
          35. let obj = null;
          36. // 如果活動未結束,對時間進行處理
          37. if (endTime - newTime > 0) {
          38. let time = (endTime - newTime) / 1000;
          39. // 獲取天、時、分、秒
          40. let day = parseInt(time / (60 * 60 * 24));
          41. let hou = parseInt(time % (60 * 60 * 24) / 3600);
          42. let min = parseInt(time % (60 * 60 * 24) % 3600 / 60);
          43. let sec = parseInt(time % (60 * 60 * 24) % 3600 % 60);
          44. obj = {
          45. day: this.timeFormat(day),
          46. hou: this.timeFormat(hou),
          47. min: this.timeFormat(min),
          48. sec: this.timeFormat(sec)
          49. }
          50. } else {//活動已結束,全部設置為'00'
          51. obj = {
          52. day: '00',
          53. hou: '00',
          54. min: '00',
          55. sec: '00'
          56. }
          57. }
          58. countDownArr.push(obj);
          59. })
          60. //每隔一秒執行一次倒計時函數, 渲染
          61. this.setData({ countDownList: countDownArr })
          62. setTimeout(this.countDown, 1000);
          63. }
          64. })

          wxml頁面代碼段

          
              
          1. <view class='tui-countdown-content' wx:for="{{countDownList}}" wx:key="countDownList">
          2. 距結束
          3. <text class='tui-conutdown-box'>{{item.day}}</text>天
          4. <text class='tui-conutdown-box'>{{item.hou}}</text>時
          5. <text class='tui-conutdown-box'>{{item.min}}</text>分
          6. <text class='tui-conutdown-box tui-countdown-bg'>{{item.sec}}</text>秒
          7. </view>

           

          wxss頁面代碼段

          
              
          1. page{
          2. background: #f5f5f5;
          3. }
          4. .tui-countdown-content{
          5. height: 50px;
          6. line-height: 50px;
          7. text-align: center;
          8. background-color: #fff;
          9. margin-top: 15px;
          10. padding: 0 15px;
          11. font-size: 18px;
          12. }
          13. .tui-conutdown-box{
          14. display: inline-block;
          15. height: 26px;
          16. width: 26px;
          17. line-height: 26px;
          18. text-align: center;
          19. background:#ccc;
          20. color: #000;
          21. margin: 0 5px;
          22. }
          23. .tui-countdown-bg{
          24. background: red;
          25. color: #fff;
          26. }
          27. .container{
          28. width: 100%;
          29. display: flex;
          30. justify-content: center;
          31. }
          32. .backView{
          33. width:690rpx;
          34. background: #fff;
          35. display: flex;
          36. flex-direction: column;
          37. margin-bottom: 30rpx;
          38. }
          39. .createDate
          40. {
          41. background: #f5f5f5;
          42. padding:15rpx 15rpx 10rpx 15rpx;
          43. line-height: 50rpx;
          44. font-size: 28rpx;
          45. color: gainsboro;
          46. text-align: center;
          47. }
          48. .backViewitem1{
          49. display: flex;
          50. flex-direction: row;
          51. height: 55rpx;
          52. align-items: center;
          53. padding:8rpx 40rpx;
          54. border-bottom: 2rpx solid #f5f5f5;
          55. }
          56. .ico
          57. {
          58. width:35rpx;
          59. height:35rpx;
          60. }
          61. .name
          62. {
          63. color: #c13176;
          64. margin-left: 20rpx;
          65. font-size: 28rpx;
          66. }
          67. .details
          68. {
          69. font-size:24rpx;
          70. letter-spacing: 2rpx;
          71. }
          72. .backViewitem2{
          73. display: flex;
          74. flex-direction: row;
          75. line-height: 35rpx;
          76. min-height: 70rpx;
          77. padding: 15rpx 40rpx 10rpx 40rpx;
          78. border-bottom: 2rpx solid #f5f5f5;
          79. }
          80. .details1
          81. {
          82. color:#888;
          83. font-size:23rpx;
          84. letter-spacing: 2rpx;
          85. }

           藍藍設計www.syprn.cn )是一家專注而深入的界面設計公司,為期望卓越的國內外企業提供卓越的UI界面設計、BS界面設計 、 cs界面設計 、 ipad界面設計 、 包裝設計 、 圖標定制 、 用戶體驗 、交互設計、 網站建設 平面設計服務。

          從前端和后端兩個角度分析jsonp跨域訪問(完整實例)

          seo達人

          如果您想訂閱本博客內容,每天自動發到您的郵箱中, 請點這里

          一、什么是跨域訪問

          舉個栗子:在A網站中,我們希望使用Ajax來獲得B網站中的特定內容。如果A網站與B網站不在同一個域中,那么就出現了跨域訪問問題。你可以理解為兩個域名之間不能跨過域名來發送請求或者請求數據,否則就是不安全的??缬蛟L問違反了同源策略,同源策略的詳細信息可以點擊如下鏈接:Same-origin_policy; 
          總而言之,同源策略規定,瀏覽器的ajax只能訪問跟它的HTML頁面同源(相同域名或IP)的資源。

          二、什么是JSONP

          JSONP(JSON with Padding)是JSON的一種“使用模式”,可用于解決主流瀏覽器的跨域數據訪問的問題。

          由于同源策略,一般來說位于 server1.example.com 的網頁無法與不是 server1.example.com的服務器溝通,而 HTML 的<script> 元素是一個例外。利用<script>元素的這個開放策略,網頁可以得到從其他來源動態產生的 JSON 資料,而這種使用模式就是所謂的 JSONP。用 JSONP 抓到的資料并不是 JSON,而是任意的JavaScript,用 JavaScript 直譯器執行而不是用 JSON 解析器解析。更具體的原理需要更多篇幅的講解,小伙伴可以自行去百度。

          三、JSONP的使用

          前端的使用示例

          JQuery Ajax對JSONP進行了很好的封裝,我們使用起來很方便。前端示例:

           $.ajax({
                      type:"GET",
                      url:"http://www.deardull.com:9090/getMySeat", //訪問的鏈接 dataType:"jsonp", //數據格式設置為jsonp jsonp:"callback", //Jquery生成驗證參數的名稱 success:function(data){ //成功的回調函數 alert(data);
                      },
                      error: function (e) { alert("error");
                      }
                  }); 
              
          • 1
          • 2
          • 3
          • 4
          • 5
          • 6
          • 7
          • 8
          • 9
          • 10
          • 11
          • 12
          • 13

          需要注意的地方是:

          • dataType,該參數必須要設置成jsonp
          • jsonp,該參數的值需要與服務器端約定,詳細情況下面介紹。(約定俗成的默認值為callback)

          后端的配合示例

          JQuery Ajax Jsonp原理

          后端要配合使用jsonp,那么首先得了解Jquery Ajax jsonp的一個特點: 
          Jquery在發送一個Ajax jsonp請求時,會在訪問鏈接的后面自動加上一個驗證參數,這個參數是Jquery隨機生成的,例如鏈接 
          http://www.deardull.com:9090/getMySeat?callback=jQuery31106628680598769732_1512186387045&_=1512186387046 
          中,參數callback=jQuery31106628680598769732_1512186387045&_=1512186387046就是jquery自動添加的。 
          添加這個參數的目的是唯一標識這次請求。當服務器端接收到該請求時,需要將該參數的值與實際要返回的json值進行構造(如何構造下面講解),并且返回,而前端會驗證這個參數,如果是它之前發出的參數,那么就會接收并解析數據,如果不是這個參數,那么就拒絕接受。 
          需要特別注意的是這個驗證參數的名字(我在這個坑上浪費了2小時),這個名字來源于前端的jsonp參數的值。如果把前端jsonp參數的值改為“aaa”,那么相應的參數就應該是 
          aaa=jQuery31106628680598769732_1512186387045&_=1512186387046

          后端接收與處理

          知道了Jquery Ajax Jsonp的原理,也知道了需要接受的參數,我們就可以來編寫服務器端程序了。 
          為了配合json,服務器端需要做的事情可以概括為兩步:

          第一步、接收驗證參數

          根據與前端Ajax約定的jsonp參數名來接收驗證參數,示例如下(使用SpringMVC,其他語言及框架原理類似)

           @ResponseBody @RequestMapping("/getJsonp") public String getMySeatSuccess(@RequestParam("callback") String callback){
              
          • 1
          • 2
          • 3
          第二步、構造參數并返回

          將接收的的驗證參數callback與實際要返回的json數據按“callback(json)”的方式構造:

           @ResponseBody
              @RequestMapping("/getMySeat") public String getMySeatSuccess(@RequestParam("callback") String callback){
                  Gson gson=new Gson(); //google的一個json工具庫 Map<String,String> map=new HashMap<>(); map.put("seat","1_2_06_12"); return callback+"("+gson.toJson(map)+")"; //構造返回值 }
              
          • 1
          • 2
          • 3
          • 4
          • 5
          • 6
          • 7
          • 8

          四、總結

          最終,前后端的相應代碼應該是這樣的: 
          前端

           $.ajax({
                      type:"GET",
                      url:"http://www.deardull.com:9090/getMySeat", //訪問的鏈接 dataType:"jsonp", //數據格式設置為jsonp jsonp:"callback", //Jquery生成驗證參數的名稱 success:function(data){ //成功的回調函數 alert(data);
                      },
                      error: function (e) { alert("error");
                      }
                  });
              
          • 1
          • 2
          • 3
          • 4
          • 5
          • 6
          • 7
          • 8
          • 9
          • 10
          • 11
          • 12

          后端

           @ResponseBody
              @RequestMapping("/getMySeat") public String getMySeatSuccess(@RequestParam("callback") String callback){
                  Gson gson=new Gson(); Map<String,String> map=new HashMap<>(); map.put("seat","1_2_06_12");
                  logger.info(callback); return callback+"("+gson.toJson(map)+")";
              }
              
          • 1
          • 2
          • 3
          • 4
          • 5
          • 6
          • 7
          • 8
          • 9

          需要注意的是:

          • 前端注意與后端溝通約定jsonp的值,通常默認都是用callback。
          • 后端根據jsonp參數名獲取到參數后要與本來要返回的json數據按“callback(json)”的方式構造。
          • 如果要測試的話記得在跨域環境(兩臺機器)下進行。

          完整的示例就是上面兩段代碼,這里就不提供Github連接了。上面的示例親測有效,如果有遇到問題的,歡迎留言提問。

          藍藍設計www.syprn.cn )是一家專注而深入的界面設計公司,為期望卓越的國內外企業提供卓越的UI界面設計、BS界面設計 、 cs界面設計 、 ipad界面設計 、 包裝設計 、 圖標定制 、 用戶體驗 、交互設計、 網站建設 、平面設計服務。

          小程序----頁面兼容h5標簽

          seo達人

          如果您想訂閱本博客內容,每天自動發到您的郵箱中, 請點這里

          有時候當小程序向后臺拿數據是一篇html標簽的文章時,把它放進小程序會發現很多標簽就不兼容,如果要一個個改又很麻煩,有沒有方法可以很快地兼容html標簽呢? 
          有個工具可以做到:wxParse 
          這里寫圖片描述 
          下載了它的壓縮包后解壓,復制wxParse文件夾放到小程序pages頁面里: 
          這里寫圖片描述 
          在wxml里引入,這里的路徑僅供參考:

          <import src="../../../../wxParse/wxParse.wxml" /> <view> //在需要放置html文本的地方使用wxParse模板 <template is="wxParse" data="{{wxParseData:content.nodes}}" /> </view>
              
          • 1
          • 2
          • 3
          • 4
          • 5

          在js里引入:

          let wxparse = require("../../../../wxParse/wxParse.js");
          Page({ /**
             * 頁面的初始數據
             */ data: {
                content: '' },
             onLoad: function(options) { var that = this;
               ..... /**
          * WxParse.wxParse(bindName , type, data, target,imagePadding)
          * 1.bindName綁定的數據名(必填)
          * 2.type可以為html或者md(必填)
          * 3.data為傳入的具體數據(必填)
          * 4.target為Page對象,一般為this(必填)
          * 5.imagePadding為當圖片自適應是左右的單一padding(默認為0,可選)
          */ wxparse.wxParse('content', 'html', result.data.content, that);
             }
              
          • 1
          • 2
          • 3
          • 4
          • 5
          • 6
          • 7
          • 8
          • 9
          • 10
          • 11
          • 12
          • 13
          • 14
          • 15
          • 16
          • 17
          • 18
          • 19
          • 20
          • 21
          • 22

          在wxss引入:

          @import "../../../../wxParse/wxParse.wxss";
          藍藍設計www.syprn.cn )是一家專注而深入的界面設計公司,為期望卓越的國內外企業提供卓越的UI界面設計、BS界面設計 、 cs界面設計 、 ipad界面設計 、 包裝設計 、 圖標定制 、 用戶體驗 、交互設計、 網站建設 、平面設計服務。

          日歷

          鏈接

          個人資料

          藍藍設計的小編 http://www.syprn.cn

          存檔

          亚洲va欧美va天堂v国产综合