最近都在復習J2E,多學習一些東西肯定是好的,而且現在移動開發工作都不好找了,有工作就推薦一下小弟唄,廣州佛山地區,謝謝了。
這篇博客要做的效果很簡單,就是把我博客的第一頁每個條目顯示在APP上,條目包括標題、摘要和狀態,如圖:
所以這篇博客將會涉及:
- 數據庫(MySql)簡單設計(建表、插入數據)
- 簡單爬蟲(用Python爬取網頁內容,寫入數據庫)
- 簡單接口開發(Struts和Hibernate)
- 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:不多說了吧這個