ติดตามผู้ใช้ข้ามเว็บไซต์ด้วย Tracking Pixel สำหรับ Web Developer
Tags: trackingpixel, webanalytics, usertracking, privateethics
Published: 2024-03-25 21:15:29

สวัสดีเพื่อนๆ โปรแกรมเมอร์ทุกคน วันนี้ผมจะมาแชร์เรื่องการทำงานของ Tracking Pixel ฟีเจอร์ยอดฮิตในวงการ Web Analytics ที่หลายคนอาจเคยได้ยินชื่อ แต่ยังสงสัยว่ามันทำงานยังไง แล้วเราจะเขียนโค้ดยังไงให้ใช้มันเก็บข้อมูลผู้ใช้ได้อย่างแนบเนียน ไปดูกันเลย!

## Tracking Pixel คืออะไร?

Tracking Pixel คือรูปภาพขนาดจิ๋ว (ขนาดแค่ 1x1 px) ที่ถูกฝังอยู่ในหน้าเว็บ มีลักษณะโปร่งใส มองไม่เห็นด้วยตาเปล่า แต่มีพลังในการเก็บข้อมูลผู้ใช้อย่างละเอียดยิบ ตั้งแต่ IP Address, อุปกรณ์ที่ใช้, ไปจนถึงพฤติกรรมการคลิก, การสกอร์ลเพจ และอีกสารพัด 

แต่จะให้รูปภาพเก็บข้อมูลได้ยังไง? คำตอบคือมันไม่ได้ทำคนเดียว แต่ต้องอาศัย JavaScript ที่ถูกเขียนมาเพื่อส่งข้อมูลกลับไปที่เซิร์ฟเวอร์นั่นเอง พูดง่ายๆ ก็คือ รูปภาพเป็นแค่ Front แต่ JS ต่างหากที่เป็นตัวเก็บข้อมูลจริงๆ

## ตัวอย่างการเขียนโค้ด Tracking Pixel

เรามาลองเขียนโค้ด Tracking Pixel อย่างง่ายที่เก็บอีเวนต์การคลิกกันนะ

```html
<img src="https://www.example.com/pixel.gif?event=click" style="display:none;" />

<script>
document.addEventListener("click", function() {
  var img = document.createElement("img");
  img.src = "https://www.example.com/pixel.gif?event=click&timestamp=" + Date.now();
  img.style.display = "none";
  document.body.appendChild(img);
});
</script>
```

จากโค้ดด้านบน เราสร้างแท็ก `<img>` ที่มี src เป็น URL ของรูปภาพพิกเซล และมีพารามิเตอร์ `event` เพิ่มเข้าไปเพื่อระบุว่าเป็นอีเวนต์คลิก

จากนั้นเราใช้ JavaScript ตรวจจับอีเวนต์คลิกผ่าน `addEventListener` เมื่อมีคลิกเกิดขึ้น เราจะสร้างแท็ก `<img>` ขึ้นมาใหม่แบบไดนามิก พร้อมแนบ timestamp ปัจจุบันเข้าไปใน URL เพื่อให้เซิร์ฟเวอร์รู้ว่าคลิกเกิดขึ้นเมื่อไหร่ แล้วซ่อนรูปนี้ไว้ (ไม่งั้นหน้าเว็บจะเต็มไปด้วยรูปพิกเซลแน่ๆ) 

เท่านี้เราก็มี Tracking Pixel แบบง่ายๆ ที่สามารถเก็บอีเวนต์คลิกได้แล้ว!

## ยิงข้อมูลไปเซิร์ฟเวอร์ภายนอก

บางครั้งเราอยากส่งข้อมูลไปให้บริการวิเคราะห์ภายนอกอย่าง Google Analytics ด้วย ซึ่งก็ไม่ยากเลย แค่เพิ่มโค้ดนิดหน่อยตามนี้

```js
// ตรวจสอบว่ามี global ID ของ Analytics ไหม
var globalId = getUserIdFromCookie("_ga");

// ส่งข้อมูลไปเซิร์ฟเวอร์ของ Analytics
var data = {
  ...userData, 
  global_id: globalId,
  // เพิ่มข้อมูลอื่นๆ ที่อยากส่ง
};

var trackingUrl = "https://www.google-analytics.com/collect";
sendData(data, trackingUrl);
```

จากโค้ดข้างบน เราเช็กว่าเว็บเราเคยถูกติดตั้ง Google Analytics ผ่านการหา ID ใน Cookie ไหม ถ้ามีก็ส่ง ID นั้นไปพร้อมกับข้อมูลอื่นๆ ผ่านฟังก์ชัน `sendData` ไปยัง URL ของ Analytics ซึ่งจะช่วยให้เราเชื่อมโยงข้อมูลจากเว็บเราเข้ากับข้อมูลอื่นๆ ในแพลตฟอร์มได้

## ระบุตัวตนผู้ใช้ข้ามเว็บ

อีกเรื่องที่ท้าทายคือการระบุว่า ผู้ใช้ที่เข้ามาในเว็บเรา เคยเข้ามาเว็บอื่นที่เราติดตามด้วยไหม  ซึ่งมีเทคนิคให้ใช้หลายอย่าง เช่น

- **Third-party Cookie**: ใช้คุกกี้ที่ Set โดยโดเมนอื่นเพื่อเชื่อมข้อมูลข้ามเว็บ แต่อาจโดนเบราว์เซอร์ปัจจุบันบล็อก
- **ID ในลิงก์**: แชร์ข้อมูลผู้ใช้ผ่าน ID บน URL ที่ลิงก์ข้ามเว็บ แต่ต้องให้เว็บอื่นร่วมมือด้วย
- **Deterministic**: ใช้ข้อมูลส่วนตัวอย่างอีเมลหรือเบอร์โทรในการแมทช์ข้อมูลข้ามเว็บ แต่ต้องรอให้ผู้ใช้ล็อกอินก่อน
- **Probabilistic**: ใช้ AI อนุมานตัวตนผู้ใช้จากข้อมูลหลายอย่าง แต่แม่นยำน้อยกว่า

ตัวอย่างการใช้ Local Storage เก็บ User ID ในเว็บเรา:

```js
var userId = localStorage.getItem("user_id");

if (!userId) {
  userId = generateRandomId();
  localStorage.setItem("user_id", userId);
}

var userData = {
  user_id: userId,
  // ข้อมูลอื่นๆ
}
```

โค้ดนี้จะเช็กว่าเคยมีการเก็บ User ID ใน Storage ไหม ถ้ายังก็สุ่มสร้างใหม่และเก็บไว้ ซึ่งจะใช้ระบุตัวตนผู้ใช้ได้จนกว่าเขาจะล้างข้อมูล




## เรามาลองเขียนโค้ดที่เก็บข้อมูลผู้ใช้แบบละเอียดลึกซึ้งกว่าเดิมกันดีกว่า 

จะได้เห็นภาพการทำงานของ Tracking Pixel ได้ชัดเจนขึ้น เริ่มจากเก็บข้อมูลพื้นฐานไปจนถึงข้อมูลพฤติกรรมเลยนะ

```html
<script>
// ฟังก์ชันส่งข้อมูลไปเซิร์ฟเวอร์
function sendData(data) {
  var img = document.createElement("img");
  img.src = "https://www.mywebsite.com/track?" + objectToQueryString(data);
  img.style.display = "none";
  document.body.appendChild(img);
}

// แปลง Object เป็น Query String
function objectToQueryString(obj) {
  return Object.keys(obj).map(key => key + '=' + encodeURIComponent(obj[key])).join('&');
}

// เก็บข้อมูลพื้นฐานของผู้ใช้
var basicData = {
  user_id: localStorage.getItem("user_id") || generateRandomId(),
  session_id: sessionStorage.getItem("session_id") || generateRandomId(), 
  page_url: window.location.href,
  referrer: document.referrer,
  screen_resolution: window.screen.width + "x" + window.screen.height,
  user_agent: navigator.userAgent,
  timestamp: Date.now()
};

// เก็บ IDs ลง Storage
localStorage.setItem("user_id", basicData.user_id);
sessionStorage.setItem("session_id", basicData.session_id);

// ส่งข้อมูลเมื่อโหลดหน้า
window.addEventListener("load", function() {
  sendData({
    ...basicData,
    event_type: "page_view" 
  });
});

// ส่งข้อมูลเมื่อมีคลิก
document.addEventListener("click", function(event) {
  sendData({
    ...basicData,
    event_type: "click",
    target_tag: event.target.tagName,
    target_url: event.target.href || ""
  });
});

// ส่งข้อมูลเมื่อสกรอลเพจ
var scrollDepth = 0;
window.addEventListener("scroll", function() {
  var percentScrolled = Math.floor((window.scrollY / document.body.scrollHeight) * 100);
  if (percentScrolled > scrollDepth) {
    scrollDepth = percentScrolled;
    sendData({ 
      ...basicData,
      event_type: "scroll",
      scroll_depth: scrollDepth + "%"
    });
  }
});

// ส่งข้อมูลเมื่อ Focus Input
document.addEventListener("focusin", function(event) {
  if (event.target.tagName === "INPUT" || event.target.tagName === "TEXTAREA") {
    sendData({
      ...basicData,
      event_type: "input_focus",
      input_id: event.target.id,
      input_name: event.target.name
    });
  }
});

// ส่งข้อมูลเมื่อกรอกฟอร์มเสร็จ
document.addEventListener("submit", function(event) {
  var formData = new FormData(event.target);
  var entries = {};
  for(var pair of formData.entries()) {
    entries[pair[0]] = pair[1];
  }

  sendData({
    ...basicData,
    event_type: "form_submit",
    form_data: JSON.stringify(entries)
  });
});

// ส่งข้อมูลทุก 30 วินาที
setInterval(function() {
  sendData({
    ...basicData,
    event_type: "heartbeat",
    timestamp: Date.now()
  });
}, 30000);

</script>
```

จากโค้ดด้านบน เราเก็บข้อมูลผู้ใช้แบบหลากหลายมากขึ้น ทั้งการ track แบบ active และ passive ดังนี้

1. **ข้อมูลพื้นฐาน** อย่าง User ID, Session ID, URL ปัจจุบัน, หน้าที่มา, ความละเอียดจอ และ Timestamp โดยเราเก็บ ID ลง Storage เพื่อใช้ครั้งถัดไป

2. **การโหลดหน้า** - เมื่อผู้ใช้โหลดหน้าเว็บ จะส่งข้อมูลประเภท "page_view" ไปที่เซิร์ฟเวอร์ 

3. **การคลิก** - เมื่อมีการคลิกเกิดขึ้น จะส่งข้อมูลประเภทคลิก ป้ายกำกับของเป้าหมาย และ URL ของลิงก์ (ถ้ามี) ไป

4. **การสกรอลเพจ** - เมื่อผู้ใช้เลื่อนหน้าเว็บ เราจะคำนวณเปอร์เซ็นต์ความลึกของการสกรอล และส่งข้อมูลกลับเมื่อมีการสกรอลลึกขึ้นกว่าเดิม

5. **การโฟกัสอินพุต** - เมื่อผู้ใช้คลิกลงบนช่องกรอกข้อมูล `<input>` หรือ `<textarea>` จะส่งข้อมูลชื่อและ ID ของฟิลด์นั้นๆ ไป

6. **การส่งฟอร์ม** - เมื่อผู้ใช้กด Submit ฟอร์ม จะเก็บข้อมูลจากฟิลด์ต่างๆ ในรูปแบบ JSON แล้วส่งไปพร้อมกับอีเวนต์ "form_submit"

7. **Heartbeat** - ทุกๆ 30 วินาที จะส่ง Timestamp ล่าสุดเข้าไป เพื่อบอกว่าผู้ใช้ยังคงใช้งานหน้าเว็บอยู่ (แบบ Passive Tracking)

นอกจากนี้เรายังแปลงข้อมูลจาก Object ให้เป็น Query String ด้วยเมื่อส่งไปที่เซิร์ฟเวอร์ เพื่อให้อ่านค่าได้ง่าย

แน่นอนว่าข้อมูลที่เก็บได้ในตัวอย่างนี้ยังไม่ครบทุกอย่าง เรายังสามารถเก็บอะไรได้อีกเยอะ เช่น:

- ตำแหน่งเมาส์ที่คลิก
- การเปิดหรือปิดวิดีโอ
- เวลาที่อยู่ในหน้าเว็บ
- การพิมพ์ลงในฟิลด์ต่างๆ
- ภาษาที่ตั้งค่าในเบราว์เซอร์
- สถานะการเข้าสู่ระบบ
- อื่นๆ อีกมากมาย

โดยรวมแล้ว Tracking Pixel เป็นเทคนิคการเก็บข้อมูลผู้ใช้แบบเบ็ดเสร็จในที่เดียว สามารถให้ข้อมูลเชิงลึกที่เป็นประโยชน์แก่ธุรกิจได้มหาศาล 

อย่างไรก็ตาม มันก็มาพร้อมกับความกังวลเรื่องความเป็นส่วนตัว เพราะผู้ใช้งานหลายคนอาจไม่คาดคิดว่าทุกการกระทำของตนกำลังถูกจับตามองและบันทึกไว้ตลอด

ดังนั้น ในฐานะโปรแกรมเมอร์ เราควรคำนึงถึงแง่มุมจริยธรรมของเทคโนโลยีนี้ด้วย ใช้มันอย่างรับผิดชอบ เคารพความเป็นส่วนตัวของผู้ใช้ และเปิดเผยว่ามีการเก็บข้อมูลอะไรบ้างอย่างโปร่งใสนะครับ

สุดท้ายนี้หวังว่าทุกคนจะได้ไอเดียการเขียน Tracking Pixel แบบครอบคลุม และเข้าใจถึงศักยภาพและข้อควรระวังในการใช้งานไปบ้างนะครับ ถ้ามีอะไรสงสัยเพิ่มเติมคอมเมนต์ถามได้เลย แล้วเจอกันใหม่บทความหน้า!