學習日記-從爬蟲到接口到APP


最近都在復習J2E,多學習一些東西肯定是好的,而且現在移動開發工作都不好找了,有工作就推薦一下小弟唄,廣州佛山地區,謝謝了。

這篇博客要做的效果很簡單,就是把我博客的第一頁每個條目顯示在APP上,條目包括標題、摘要和狀態,如圖:

 

所以這篇博客將會涉及:

  1. 數據庫(MySql)簡單設計(建表、插入數據)
  2. 簡單爬蟲(用Python爬取網頁內容,寫入數據庫)
  3. 簡單接口開發(Struts和Hibernate)
  4. APP網絡請求(Retrofit、Gson、RxJava等)

大體的流程就是:先創建數據庫,通過爬蟲手段爬取博客首頁的條目內容並填充至數據庫,接着搭建簡單的JavaWeb后台,提供接口訪問,通過網絡請求返回數據庫中的數據。

 

① 數據庫設計

要爬取數據和接口開發,肯定都是需要先創建數據庫和數據表的。這里使用的是MySql,操作的工具是Navicat。對於上面的數據,我們需要建立對應的表:

其中id為主鍵且自增長。創建完畢可以進行插入和刪除等測試。

 

② 爬取網頁數據

靜態網頁的爬取是比較簡單的,其實就是根據網頁源碼進行解析匹配,而Python的正則表達式較為強大,所以這里使用Python來進行操作,另外,基礎的爬蟲也可以使用一些庫來簡化操作,這里會用到request和bs4兩個,request用於網絡請求,而bs4則是用於解析網頁源碼得到我們想要的數據。最后,通過MySQLdb對數據進行存儲。

先分析網頁源碼,可以使用Chrome來觀察結構:

得到結構之后就可以進行編碼:

文件名:MySpider.py

 1 #coding=utf-8
 2 
 3 import sys
 4 import requests
 5 from bs4 import BeautifulSoup
 6 import MySQLdb
 7 
 8 reload(sys)
 9 sys.setdefaultencoding('utf-8')
10 
11 # 定義一個博客類
12 class Blog:
13     title = ""
14     desc = ""
15     postDate = ""
16     status = ""
17 
18 # 進行網絡請求拉取源碼,地址為我博客首頁
19 response = requests.get("http://www.cnblogs.com/Fndroid/")
20 # 使用BeautifulSoup進行處理
21 soup = BeautifulSoup(response.text, "html.parser")
22 
23 blogs = []
24 # 根據源碼格式爬取欄目,找到class為day的標簽,獲取並遍歷其子div
25 for day in soup.findAll(class_='day'):
26     divs = day.findAll("div")
27     n = 0
28     b = Blog()
29     for div in divs:
30         if n == 0:
31             # 爬取發表時間
32             b.postDate = div.a.string
33         elif n == 1:
34             # 爬取標題
35             b.title = div.a.string
36         elif n == 2:
37             # 爬取摘要
38             b.desc = div.div.contents[0]
39         elif n == 5:
40             # 爬取文章狀態
41             b.status = div.contents[0]
42         elif n == 6:
43             n = 0
44             blogs.append(b)
45             break
46 
47         n += 1
48 
49 # 連接數據庫,數據庫用戶名root,密碼root,數據庫名myblog,編碼格式utf8
50 db = MySQLdb.connect("localhost", "root", "root", "myblog", charset="utf8")
51 cursor = db.cursor()
52 
53 for bl in blogs:
54     sub_sql = "'"+bl.title+"','"+bl.desc+"','"+bl.postDate+"','"+bl.status+"'"
55     # 構造sql語句,插入數據
56     sql = "insert into Blog(title,description,post_date,post_status) values("+sub_sql+")"
57     try:
58         cursor.execute(sql)
59         db.commit()
60     except:
61         db.rollback()
62 
63 db.close()

主要的功能步驟已經在源碼注釋中標注了。接着運行程序,查看數據庫內容如下則表示正確:

其中id值只要是自增長即可,可以不與上圖對應。另外,如果APP需要點擊條目跳轉到博客內容,還需要把url獲取下來,這里只是簡單的事例就不拉了。

 

③ 接口開發

這個接口其實也很好理解,就是通過一個URL訪問得到對應的數據,數據格式可以是JSON或者Xml,我們通過這些數據進行頁面顯示等等。而我們這里使用的是J2E中的Struts和Hibernate來搭建這個簡單的后台。Struts用來攔截請求、Hibernate用於操作數據庫。

實際上,Python也是可以做到的,但是目前國內很多中小企業都是用的J2E,所以......

環境搭建什么的就不說了,網上一搜一大堆,或者直接使用MyEclipse,快捷方便。

首先,編寫對應於數據庫的實體Bean:

 1 public class Blog implements java.io.Serializable {
 2 
 3     // Fields
 4 
 5     private Integer id;
 6     private String title;
 7     private String description;
 8     private String postDate;
 9     private String postStatus;
10 
11     // Constructors
12 
13     /** default constructor */
14     public Blog() {
15     }
16 
17     /** full constructor */
18     public Blog(String title, String description, String postDate, String postStatus) {
19         this.title = title;
20         this.description = description;
21         this.postDate = postDate;
22         this.postStatus = postStatus;
23     }
24 
25     // Property accessors
26 
27     public Integer getId() {
28         return this.id;
29     }
30 
31     public void setId(Integer id) {
32         this.id = id;
33     }
34 
35     public String getTitle() {
36         return this.title;
37     }
38 
39     public void setTitle(String title) {
40         this.title = title;
41     }
42 
43     public String getDescription() {
44         return this.description;
45     }
46 
47     public void setDescription(String description) {
48         this.description = description;
49     }
50 
51     public String getPostDate() {
52         return this.postDate;
53     }
54 
55     public void setPostDate(String postDate) {
56         this.postDate = postDate;
57     }
58 
59     public String getPostStatus() {
60         return this.postStatus;
61     }
62 
63     public void setPostStatus(String postStatus) {
64         this.postStatus = postStatus;
65     }
66 
67 }

接着在對應包中編寫一個Blog.hbn.xml文件,用於Hibernate數據映射:

 1 <?xml version="1.0" encoding="utf-8"?>
 2 <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
 3 "http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">
 4 <hibernate-mapping>
 5     <class name="com.fndroid.entity.Blog" table="blog" catalog="myblog">
 6         <id name="id" type="java.lang.Integer">
 7             <column name="id" />
 8             <generator class="identity" />
 9         </id>
10         <property name="title" type="java.lang.String">
11             <column name="title" not-null="true" />
12         </property>
13         <property name="description" type="java.lang.String">
14             <column name="description" not-null="true" />
15         </property>
16         <property name="postDate" type="java.lang.String">
17             <column name="post_date" not-null="true" />
18         </property>
19         <property name="postStatus" type="java.lang.String">
20             <column name="post_status" not-null="true" />
21         </property>
22     </class>
23 </hibernate-mapping>

如果你使用MyEclipse,則這些文件可以通過自帶的MyEclipse Hibernate工具生成。

接着,創建一個Dao來獲取數據庫內容:

 1 public class BlogDao {
 2 
 3     public List<Blog> getBlogs() {
 4         Configuration conf = new Configuration().configure();
 5         SessionFactory sessionFactory = conf.buildSessionFactory();
 6         Session session = sessionFactory.openSession();
 7         Query query = session.createQuery("from Blog");
 8         List<Blog> list = query.list();
 9         return list;
10     }
11 }

最后創建並配置一個Action來攔截請求,並填充數據,這里使用Gson來進行數據包裝,所以要記得導入Gson的jar包:

 1 public class BooksAction extends ActionSupport {
 2     
 3     @Override
 4     public String execute() throws Exception {
 5         BlogDao dao = new BlogDao();
 6         List<Blog> blogs = dao.getBlogs();
 7         String result = createJsonString(!blogs.isEmpty(), blogs);
 8         HttpServletRequest request = ServletActionContext.getRequest();
 9         // 將數據填充至內置對象request中,這樣在jsp中可以獲取得到
10         request.setAttribute("json", createJsonString(!blogs.isEmpty(), blogs));
11         return "success";
12     }
13     
14     /**
15      * 通過數據集生成JSON格式的數據
16      * @param res 數據集是否為空
17      * @param blogs 數據集
18      * @return
19      */
20     private String createJsonString(boolean res, List<Blog> blogs) {
21         JsonObject resultJson = new JsonObject();
22         JsonArray array = new JsonArray();
23         resultJson.addProperty("result", res? 1:0);
24         resultJson.addProperty("err_msg", res? "服務器成功返回數據":"服務器錯誤");
25         if (res){
26             for (Blog blog : blogs) {
27                 JsonObject bObject = new JsonObject();
28                 bObject.addProperty("id", blog.getId());
29                 bObject.addProperty("title", blog.getTitle());
30                 bObject.addProperty("desc", blog.getDescription());
31                 bObject.addProperty("post_date", blog.getPostDate());
32                 bObject.addProperty("status", blog.getPostStatus());
33                 array.add(bObject);
34             }
35         }
36         resultJson.add("blogs", array);
37         return resultJson.toString();
38     }
39 }

配置Struts.xml:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE struts PUBLIC "-//Apache Software Foundation//DTD Struts Configuration 2.1//EN" "http://struts.apache.org/dtds/struts-2.1.dtd">
<struts>
    <package name="books" extends="struts-default">
        <action name="listBlogs" method="execute" class="com.fndroid.action.BooksAction">
            <result name="success">success.jsp</result>
        </action>
    </package>
</struts>    

success.jsp

<%@ page language="java" import="java.util.*" pageEncoding="utf-8"%>
<%=request.getAttribute("json") %>

直接顯示request內置對象中對應的json格式值即可。

接口已經編寫完畢,接着啟動服務器,並且布置項目,這個時候可以用瀏覽器訪問http://localhost:8080/WebDemo/listBlogs來看是否成功,其中WebDemo為項目名稱,listBlogs為Action名:

瀏覽器效果:

這肯定是不夠直觀的,所以我們可以嘗試一下一些用於開發的請求分析工具,例如Postman(Chrome應用商店下載):

這樣就可以看到對應的格式。

 

④ APP編寫

萬事俱備,只欠東風了。APP的內容也不多,通過一個RecyclerView顯示每個條目的標題、摘要和狀態即可。

初始化使用空列表構造一個RecyclerView,接着通過RxJava和Retrofit進行網絡請求,得到數據傳遞給數據列表並刷新界面。

注意:以下代碼可能會出現令人身體不適的Lumbda表達式

主界面布局代碼省略,里面只有一個RecyclerView。

編寫RecyclerView每個Item的布局(使用數據綁定):

 1 <?xml version="1.0" encoding="utf-8"?>
 2 <layout xmlns:android="http://schemas.android.com/apk/res/android">
 3 
 4     <data>
 5 
 6         <variable
 7             name="blog"
 8             type="com.fndroid.retrofitdemo.Blogs.BlogsBean"/>
 9     </data>
10 
11     <LinearLayout
12         android:layout_marginTop="8dp"
13         android:layout_marginBottom="8dp"
14         android:layout_width="match_parent"
15         android:layout_height="wrap_content"
16         android:orientation="vertical">
17 
18         <TextView
19             android:textSize="16sp"
20             android:textAlignment="center"
21             android:text="@{blog.title}"
22             android:layout_width="match_parent"
23             android:layout_height="wrap_content"/>
24 
25         <TextView
26             android:textStyle="italic"
27             android:text="@{blog.desc}"
28             android:layout_width="match_parent"
29             android:layout_height="wrap_content"/>
30 
31         <TextView
32             android:text="@{blog.status}"
33             android:layout_width="match_parent"
34             android:layout_height="wrap_content"/>
35 
36     </LinearLayout>
37 
38 </layout>

創建Recycler的Adapter:

 1 public class MyAdapter extends RecyclerView.Adapter {
 2     private Context mContext;
 3     private ArrayList<Blogs.BlogsBean> mBlogsArrayList;
 4     public MyAdapter(Context context, ArrayList<Blogs.BlogsBean> blogsArrayList) {
 5         this.mContext = context;
 6         this.mBlogsArrayList = blogsArrayList;
 7     }
 8 
 9     @Override
10     public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
11         // 獲取綁定實例,並存儲在ViewHolder中
12         ItemBlogBinding binding = DataBindingUtil.inflate(LayoutInflater.from(mContext), R.layout
13                 .item_blog, parent, false);
14         VH vh = new VH(binding.getRoot());
15         vh.binding = binding;
16         return vh;
17     }
18 
19     @Override
20     public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
21         Blogs.BlogsBean blogsBean = mBlogsArrayList.get(position);
22         VH vh = (VH) holder;
23         // 設置數據綁定數據源
24         vh.binding.setVariable(com.fndroid.retrofitdemo.BR.blog, blogsBean);
25     }
26 
27     @Override
28     public int getItemCount() {
29         return mBlogsArrayList.size();
30     }
31 
32     class VH extends RecyclerView.ViewHolder{
33         ItemBlogBinding binding;
34         public VH(View itemView) {
35             super(itemView);
36         }
37     }
38 }

編寫Retrofit的請求服務:

public interface IdentifyService{
    @GET("listBlogs")
    public Observable<Blogs> getBlogs();
}

最后編寫Activity的內容:

 1 public class MainActivity extends AppCompatActivity {
 2     private static final String TAG = "MainActivity";
 3 
 4     // 這里不能寫localhost,因為模擬器和服務器ip不同
 5     private static final String URL = "http:192.168.1.181:8080/WebDemo/";
 6     private ArrayList<Blogs.BlogsBean> mBlogsArrayList;
 7     private MyAdapter mMyAdapter;
 8 
 9     @BindView(R.id.main_rv)
10     RecyclerView mRecyclerView;
11 
12     @Override
13     protected void onCreate(Bundle savedInstanceState) {
14         super.onCreate(savedInstanceState);
15         setContentView(R.layout.activity_main);
16         ButterKnife.bind(this);
17 
18         mRecyclerView.setLayoutManager(new LinearLayoutManager(this));
19         // 傳入空數據源
20         mBlogsArrayList = new ArrayList<>();
21         mMyAdapter = new MyAdapter(this, mBlogsArrayList);
22         mRecyclerView.setAdapter(mMyAdapter);
23 
24         // 使用Gson解析數據,用RxJava2封裝請求
25         Retrofit ret = new Retrofit.Builder().baseUrl(URL).addConverterFactory
26                 (GsonConverterFactory.create()).addCallAdapterFactory(RxJava2CallAdapterFactory
27                 .create()).build();
28         IdentifyService identifyService = ret.create(IdentifyService.class);
29         Observable<Blogs> blogs = identifyService.getBlogs();
30         blogs.subscribeOn(Schedulers.newThread()).observeOn(AndroidSchedulers.mainThread())
31                 .subscribe(b -> {
32                     mBlogsArrayList.addAll(b.getBlogs());
33                     mMyAdapter.notifyDataSetChanged();
34                 });
35     }
36 }

因為使用了各種框架,所以內容也很簡單,畢竟都2016年了,誰不用框架對嗎。

這里對使用的各種框架做一個簡單的說明:

  • RxJava2:異步請求必須要掌握的
  • Retrofit:它聰明的提供了Gson、RxJava2等支持,底層也是基於okhttp,所以性能也較好,也是必須掌握的
  • databinding(數據綁定):官方出品,免去setText、findViewById等冗余代碼
  • RetroLumbda:在Java7上提供Lumbda語言支持,畢竟官方默認1.7,改為1.8會導致Instant Run失效,所以你懂的
  • Butterknife:不多說了吧這個

 

源碼地址


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM