這篇主要紀錄一下之前寫的一個小玩具
主要是分為兩個部分
第一部分是Python + selenium 爬Ptt網頁版的資料並儲存在 AWS DynamoDB
這裡主要是改以前寫的一個爬蟲程式, 以前寫的時候那時用的是python2.7版本並使用Mechanize 與 BeautifulSoup, 最近把它改成python3以及selenium連結, 目前還會有一些舊的程式碼在裡面。
程式可以指定要對哪個版來爬資料, 並可以指定要幾小時內的資料, 目前針對推文數高於一定程度以上的文章,另外使用FB API去取該連結的互動狀況, 用該資料簡單去算一個分數(可以在下面的測試結果畫面看到)用來作為熱門文章的排序, 這個數字的好處是可以看到實際在FB的熱門狀況, 因為有時候PTT推文數高的文章不一定是值得分享的文章, 也有可能是閒聊文。
另一part是將得到的資料寫進 AWS 的DynamoDB當中, 程式碼在這,會使用DynamoDB的原因主要是因為我的AWS 免費已經過期了, 所以我選擇使用目前還是有免費方案的DynamoDB。
主要使用了兩張表, 一張是索引, 主要紀錄各個板對應到另一張表的資料ID,另一張表就是爬下來的資料,以版名以及爬資料的時間當作ID來儲存。
第二部分是使用LintBot api加上AWS lambda回應上一步存在DynamoDB中的資料
會使用lambda其實是一個湊巧, 一開始本來是要開一個EC2加上Nodejs或Python+flask來回應linebot webhook, 但是想一想我已經沒有免費Tier了, 作為一個小玩具成本有點高, 後來在查資料的時候看到這篇文章, 覺得這樣server less的架構成本低, 又不需要一直開著, 也可以使用API Gateway來開一個https的api當作webhook。
這裡當初在做的時候本來要讓linebot在資料有更新時自動使用push來通知使用者, 但是由於push功能需要付費, 所以就作罷了。
下面就是成果

可以加入這個LineBot來測試
以下是測試結果畫面
第一張圖, 隨便打幾個字可以得到目前有資料的版名

第二張圖, 輸入一個有資料的版名後, 可以得到爬蟲爬到的資料, 標題後是其分數

最近寫了一個幫助自己解決一個煩人雜事的小程式gist連結
這支程式主要工作如下:
登入公司的jira, 從手上的issue中找出指定符合父issue ID的所有issue, 統一填上註解並轉給指定的人。
寫的比較匆忙沒有加太多的例外判定, 目標是快速解決手邊的雜事。
之所以選擇selenium主要還是看上他可以省去很多功, 以前寫的爬蟲主要還是使用mechanize + BeautifulSoup, 但是遇到js就會很麻煩, 後來使用selenium不只js部分的問題沒有了, 連BeautifulSoup都不需要了, 他的find方法已經足夠我使用。
事前selenium module的安裝就不提, 要注意的是webdriver供許多瀏覽器版本可以用, 我用的是chrome, 必須要事前下載 chromedriver 使用, 並在使用時設置路徑, 如webdriver.Chrome(executable_path=r'.\\chromedriver.exe') 。
這支程式主要使用selenium幾個功能:
api
selenium 提供了一系列的方法讓你去找到目標元件, 基本上都如同api所列的一樣, 你可以依照條件使用特定id或者class來查找, 唯獨比較特別的是如果id或class中間有空格的最好還是使用 find_element_by_xpath ,之前沒有看過XPath所以花了一點時間才清楚使用方法。
基本上消耗最多時間還是在翻網頁原始碼找特定元素的id或class, 找到後基本上都不會有太大的問題,
找到元素後可以做一些行為, 這裡比較常用的有
a跟buttoninput有時候在進行一些動作後, 需要等待特定元件出現才可以進行下一個動作, 此時就需要用wait功能, 這裡有一篇文章提到使用方法, 寫的很詳細。
最近嘗試將一些效能消耗較大的NGUI UI轉換成UGUI測試, 不過目前暫時是自行測試, 所以在圖集部分暫時沿用以前的NGUI atlas來使用, 只是UGUI是不吃NGUI的atlas只吃sprite, 所以寫了一個工具來將NGUI的atlas資料寫到Texture的設定當中。
最主要是要把讀取到SpriteData的y軸做額外的反轉處理.
目前的專案server端與client端間的共用邏輯是用C++寫的, 而Unity的主要使用程式碼又是c#, 所以有需要從c#呼叫c++端的方法, 以及從c++取資料, 這裡主要整理一下專案用的方法以及遇到的一些問題。
<!--more-->Windows是編成dll, android是編成.so, ios主要是編成.a
溝通方式有分兩種:
PInvoke (DllImport 屬性)
https://msdn.microsoft.com/zh-tw/library/eyzhw3s8.aspx
[DllImport(PlatformDllName)]
private static extern void TEST_Init();
如此宣告後可以直接呼叫直接呼叫
在Unity上面使用DllImport後會鎖死該dll檔, 想要更新的話要關閉Unity
It Just Works (IJW) 使用Marshal類別
https://forum.unity3d.com/threads/unloading-native-plugins-in-the-unity-editor.198296/
做起來挺麻煩, 但是可以避免dll不能unload問題
前期使用的是第二種方式, 但是後來覺得需要更多時間去維護還是都改用第一種
ref: 從 Managed 程式碼呼叫原生函式 https://msdn.microsoft.com/zh-tw/library/ms235282.aspx
簡單的呼叫範例
c++用的巨集
#ifdef _MSC_VER #define PLUGIN_DLL_API __declspec(dllexport) #else #define PLUGIN_DLL_API #endif
c++端的方法
extern "C" {
PLUGIN_DLL_API void Test_Func(int StartIdx, int Count, int* OutArray)
{
...
}
};
c#的lib名稱宣告
#if (UNITY_IOS && !UNITY_EDITOR_OSX) || UNITY_XBOX360 // On iOS and Xbox 360 plugins are statically linked into // the executable, so we have to use **Internal as the // library name. public const string PlatformDllName = "__Internal"; #else public const string PlatformDllName = "Name"; // 不用加lib. #endif
c#端方法宣告
[DllImport(SysPluginBase.PlatformDllName)]
private static extern void Test_Func(int StartIdx, int Count, System.IntPtr OutArray);
要注意的有兩點:
System.IntPtr來處理。EntryPointNotFoundException。簡單的結構範例
c++端
class Data
{
public:
int m_Int; // int
char m_Str[32]; // 字串
int m_IntAry[10]; // int陣列
};
c#端
[StructLayout(LayoutKind.Sequential, Pack = 4)] [Serializable] public class Data { public int m_Int; // int [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)] public string m_Str; // 字串 [MarshalAs(UnmanagedType.ByValArray, SizeConst = 10)] public int[] m_IntAry; // int陣列 };
LayoutKind.Sequential主要是告知這個結構的記憶體是照順序排下來, 也可以宣告成 Explicit 來自己指定, 不過用 Sequential 是比較簡單的作法, 只要注意順序不要有錯誤就行了
最基本的作法, 是傳該結構的指標到c#(IntPtr), 再使用Marshal.PtrToStructure來轉換成c#用的結構
extern "C" {
PLUGIN_DLL_API Data* Test_Func(){ ... }
};
在取IntPtr時需要檢查是否為null, 可以檢查該值是否為IntPtr.Zero
[DllImport(SysPluginBase.PlatformDllName)]
private static extern System.IntPtr Test_Func();
public Data GetData()
{
System.IntPtr DataPtr = Test_Func();
if (DataPtr == System.IntPtr.Zero)
return null;
Data Dta = (Data)Marshal.PtrToStructure(DataPtr, typeof(Data));
return Dta;
}
除了最基本的Marshal.PtrToStructure方法以外, 還有幾種其他的方法可以取得結構的值, 由於實在太多程式碼用gist來存
iOS要使用CallBack C++Plugin, MonoPInvokeCallback 是一定要加的
https://developer.xamarin.com/guides/ios/advanced_topics/limitations/#Reverse_Callbacks
原文節錄如下:
When using the ahead-of-time compiler required by the iPhone there are two important limitations at this point:
You must flag all of your callback methods with the MonoPInvokeCallbackAttribute
The methods have to be static methods, there is no support for instance methods.
除了要在方法上方加上[AOT.MonoPInvokeCallback(typeof(對應該方法的delegate))]外, 該方法還需要是static方法.
PC端跟android不需要
在android上如果結構使用pack = 1 , 在加上該struct有float的話, c#在使用Marshal.PtrToStructure就會造成crash, 只有android會有這個問題, 該問題找不到確切造成的原因。
結構中不要使用bool, 因為無法保證他的位元長度, 最好的方法還是用byte之類的代替。
一開始專案在處理手勢的部份大多是自行處理, 以Unity的狀況來說如果是單指點擊來說寫起來沒有什麼大問題, 但後來專案中有需要處理Pinch與Twist兩種手勢, 一旦進入需要兩隻手指以上的手勢就開始麻煩了, 主要還是因為PC開發環境在多指的行為上需要自行模擬, 以及可能需要用執行環境來判斷來做不同的處理.
#if UNITY_EDITOR
if (Input.GetAxis("Mouse ScrollWheel") != 0)
{
...
}
#elif UNITY_ANDROID || UNITY_IOS
if (Input.touchCount == 2)
{
...
}
#endif
一開始的作法是用替代的方式來取代, 比方說PC端用滑鼠滾輪來取代Twist, 但是用久了感覺不是很直覺, 而且維護起來確實比較麻煩, 要抓真實的感覺需要編譯apk至手機上測試, 於是之後就改用這個套件。
這個套件對我的好處主要在於可以省略多平台的一些實作細節, 而且在PC端就可以模擬Pinch與Twist手勢(可選擇組合鍵), 測試起來比較方便。 {% asset_img 1.png [組件設置] %}
事件提供了以下幾種
LeanTouch.OnFingerDown
LeanTouch.OnFingerSet
LeanTouch.OnFingerUp
LeanTouch.OnFingerTap
LeanTouch.OnFingerSwipe
LeanTouch.OnGesture
除了前幾個基本的事件以外, 也可以用LeanTouch.OnGesture來處理複雜的手勢,另外也提供了相關的取值方法:
LeanGesture.GetTwistDegrees
LeanGesture.GetPinchScale
LeanGesture.GetScreenDelta
不用再去計算一些細節比方說pinch兩指間移動的量之類的。
看一些文章有提到該套件有一些組件可以提供一些預設行為可供套用, 雖然沒有用到但感覺也是挺方便的。